Limited Time Offer!
For Less Than the Cost of a Starbucks Coffee, Access All DevOpsSchool Videos on YouTube Unlimitedly.
Master DevOps, SRE, DevSecOps Skills!
The real issue was not Laravel. Laravel was only redirecting users to Keycloak. The mess was inside Keycloak’s first broker login flow, Google IdP settings, and old UPDATE_PASSWORD required actions left on migrated users. This article follows the exact fix brief you provided.
Table of Contents
- The Real Problem: Keycloak Google Login Activation Email
- Why This Happens in Keycloak
- Step A: Duplicate the Built-in First Broker Login Flow
- Step B: Configure the Duplicated Flow Requirements
- Step C: Bind the New Flow to the Google Identity Provider
- Step D: Disable UPDATE_PASSWORD Required Action Realm-Wide
- Step E: Clean Pending UPDATE_PASSWORD for Migrated Users
- Step F: Optional Laravel Code-Side Hardening
- Common Gotchas
- How to Verify the Fix
- Conclusion
- FAQ
The Real Problem: Keycloak Google Login Activation Email
The main symptom was simple: Keycloak Google login activation email behavior appeared even though Google had already verified the user’s email.
Our stack looked like this:
- Laravel application
- Self-hosted Keycloak
- Google configured as an Identity Provider
- Login redirected with
idp_hint=google - Some users already existed in Keycloak because they were migrated earlier
The symptoms were annoying:
- Brand-new Google users received an email verification link.
- Existing users saw an “Account already exists” page.
- After linking, some users were pushed into a “set new password” screen.
- This happened even for users who should only ever log in through Google.
That last point matters.
If your product uses Google SSO as the primary login method, asking users to set a local Keycloak password makes no sense. It creates support tickets, confusion, and that classic “but I already logged in” complaint.
The goal was:
- Trust Google’s verified email.
- Auto-link Google users to existing Keycloak users by email.
- Skip the “confirm link existing account” page.
- Skip email verification.
- Skip password reset for Google-only users.
- Keep the flow clean for Laravel.
Why This Happens in Keycloak
Keycloak’s default first broker login flow is cautious by design. It is used when a user logs in through an external Identity Provider for the first time and Keycloak does not yet have that external identity linked to a local user. Keycloak’s docs describe the First Broker Login flow as the flow used during first login with an identity provider. (Keycloak)
That caution is useful in many enterprise environments.
But in a Google SSO flow, it can feel too cautious.
Here were the root causes:
Root Cause A: Default First Broker Login Flow Verifies Existing Accounts
When Keycloak detects an email match, the default flow can run “Verify existing account by Email.”
That means:
- Google says the email is verified.
- Keycloak still says, “Cool, but let me verify that again.”
- User receives an activation or verification email.
This is where the Keycloak Google login activation email problem starts.
Root Cause B: Google Trust Email Was Off
The Google IdP setting Trust Email was disabled.
When Trust Email is off, Keycloak does not fully trust the email_verified claim from Google. So even though Google has already verified the email, Keycloak behaves like it still needs proof.
Root Cause C: Migrated Users Had UPDATE_PASSWORD Pending
Some users were migrated into Keycloak with this required action:
UPDATE_PASSWORD
So even after Google auto-linking worked, Keycloak still forced them to set a local password.
That created the keycloak update password loop feeling.
What’s actually going on under the hood
Google login and Keycloak local credentials are separate things. Linking a Google identity to a Keycloak user does not automatically clear old required actions. If
UPDATE_PASSWORDis still pending, Keycloak will still ask for it.
Step A: Duplicate the Built-in First Broker Login Flow
Go to:
Authentication → Flows → first broker login
Open the action menu and duplicate it.
Use this name:
first broker login - auto link
Why duplicate?
Because Keycloak does not allow you to safely modify built-in flows in the way we need. If you try to add executions directly to a built-in flow, you can hit this error:
It is illegal to add execution to a built in flow
So don’t fight Keycloak here. Duplicate first.
Keycloak’s own guidance for changing first broker login behavior also starts by duplicating the flow before editing it, so this is the safe path. (Keycloak)
Image suggestion: screenshot of Authentication → Flows showing the duplicated first broker login - auto link flow.
Step B: Configure the Duplicated Flow Requirements
Now edit your duplicated flow.
The final target structure should look like this:
first broker login - auto link
├── Review Profile DISABLED
├── User creation or linking REQUIRED
│ ├── Create User If Unique ALTERNATIVE
│ └── Handle Existing Account ALTERNATIVE
│ ├── Automatically set existing user REQUIRED
│ ├── Confirm link existing account DISABLED
│ └── Account verification options DISABLED
│ └── (children stay DISABLED)
└── Conditional Organization CONDITIONAL
The important change is here:
Handle Existing Account
└── Automatically set existing user REQUIRED
This is the idp-auto-link behavior.
It tells Keycloak:
“An existing local user was found. Automatically set that existing user instead of asking the user to confirm the link by email.”
Disable Review Profile
Set:
Review Profile = DISABLED
This avoids showing the profile review page when Google already provides the expected email/profile data.
Keep User Creation or Linking Required
Set:
User creation or linking = REQUIRED
Inside it:
Create User If Unique = ALTERNATIVE
Handle Existing Account = ALTERNATIVE
This means:
- If no user exists, create one.
- If an email match exists, handle the existing account.
Add Automatically Set Existing User Inside Handle Existing Account
This is the critical part.
You must add:
Automatically set existing user = REQUIRED
inside:
Handle Existing Account
Not at the root.
Not beside User creation or linking.
Inside the existing account sub-flow.
Critical gotcha
Automatically set existing usermust be insideHandle Existing Account. If you add it at the root level, Keycloak does not have the duplicated-user context available.
Here is the error you may see if you add it in the wrong place:
AuthenticationFlowException: Unexpected state. There is no existing duplicated user identified in ClientSession
at IdpAutoLinkAuthenticator.authenticateImpl
In the browser, Keycloak may show a misleading message:
Invalid username or password
That error is not really about the Google password. It means the flow structure is wrong.
The duplicated user reference exists only inside the Handle Existing Account context. If idp-auto-link runs before that context exists, Keycloak has no existing user to attach.
Disable Confirm Link Existing Account
Set:
Confirm link existing account = DISABLED
This removes the “Account already exists” confirmation page.
This matters because Trust Email alone will not skip the account confirmation page. Trust Email tells Keycloak whether the external email can be trusted. It does not automatically remove the confirm-link step.
Disable Account Verification Options
Set:
Account verification options = DISABLED
Also keep its children disabled.
That removes the old email verification path from this Google-first login journey.
Gotcha
If a REQUIRED sub-flow has all children disabled, Keycloak can fail in confusing ways. Keep the parent and child requirements aligned exactly as shown above.
Step C: Bind the New Flow to the Google Identity Provider
Now go to:
Identity Providers → google → Settings
Set:
Trust Email = ON
First Login Flow override = first broker login - auto link
Sync Mode = Force
Then click Save.
Do not just toggle and leave the page. Some Keycloak versions are not forgiving if you forget to save.
Why Trust Email Must Be On
Google sends an email_verified claim.
When Trust Email is enabled, Keycloak trusts that claim and does not treat the email as unverified.
This is what helps remove the skip verify email keycloak issue for Google users.
Why First Login Flow Override Matters
You do not need to change the global first broker login flow for every Identity Provider.
Bind your custom flow only to Google:
First Login Flow override = first broker login - auto link
That keeps this behavior scoped.
Sync Mode: Force vs Import
Use:
Sync Mode = Force
when you want Keycloak to update user data from Google during login.
Use:
Sync Mode = Import
when you only want Keycloak to import user data once during first login and then keep local values afterward.
For most Google SSO setups where Google is the source of truth for profile basics, Force is usually the cleaner option.
Gotcha
Sync Mode does not fix account linking by itself. It controls how brokered user data is synced. You still need the first broker login flow changes.
Step D: Disable UPDATE_PASSWORD Required Action Realm-Wide
Now go to:
Authentication → Required Actions → Update Password
Set:
Enabled = Off
Default Action = unchecked
This prevents Keycloak from assigning UPDATE_PASSWORD as a default required action going forward.
This is especially important for Google-only realms.
If the user never needs a local Keycloak password, forcing password update is just noise.
Gotcha
Disabling a required action does not automatically remove that action from existing users. It only affects future behavior.
Step E: Clean Pending UPDATE_PASSWORD for Migrated Users
Before running SQL, back up the Keycloak database.
Seriously. Do not skip this.
Then run:
DELETE FROM user_required_action
WHERE required_action = 'UPDATE_PASSWORD'
AND user_id IN (
SELECT id FROM user_entity
WHERE realm_id = (SELECT id FROM realm WHERE name = '<your-realm>')
);
Replace:
<your-realm>
with your actual realm name.
For example:
DELETE FROM user_required_action
WHERE required_action = 'UPDATE_PASSWORD'
AND user_id IN (
SELECT id FROM user_entity
WHERE realm_id = (SELECT id FROM realm WHERE name = 'mhn')
);
This clears the pending password reset action from already-migrated users.
That is what stops the keycloak update password loop for existing Google users.
Production warning
Run a backup first. Then test this query in staging. Then run it in production during a safe maintenance window if your user base is active.
Step F: Optional Laravel Code-Side Hardening
If your Laravel app provisions Keycloak users through the Admin API, check your user creation logic.
You may have something like this:
// before
$requiredActions = $requirePasswordUpdate ? ['UPDATE_PASSWORD'] : [];
For a Google-only realm, change it to:
// after
$requiredActions = [];
That prevents new users from being created with UPDATE_PASSWORD attached.
A more defensive version:
$isGoogleOnlyRealm = true;
$requiredActions = [];
if (!$isGoogleOnlyRealm && $requirePasswordUpdate) {
$requiredActions[] = 'UPDATE_PASSWORD';
}
If your app supports both password login and Google login, do not blindly remove password actions for every user. Apply this only where it makes sense.
Example:
$requiredActions = [];
if ($loginMode === 'password' && $requirePasswordUpdate) {
$requiredActions[] = 'UPDATE_PASSWORD';
}
The rule is simple:
- Google-only user? Do not require local password update.
- Password-based user? Required password update may still be valid.
Common Gotchas
Gotcha 1: “It is illegal to add execution to a built in flow”
You edited the built-in flow directly.
Fix:
Duplicate first broker login first.
Edit the duplicated flow.
Bind the duplicated flow to Google.
Gotcha 2: “Invalid username or password” After Flow Edits
This is often not a password issue.
Common causes:
Automatically set existing userwas added at the root level.- It was not added inside
Handle Existing Account. - A REQUIRED sub-flow has all children disabled.
- Flow requirements are inconsistent.
Fix the tree structure first.
Gotcha 3: Required Actions Do Not Retroactively Clear Existing Users
Turning off Update Password does not clean users who already have:
UPDATE_PASSWORD
in user_required_action.
That is why the SQL cleanup matters.
Gotcha 4: Trust Email Alone Is Not Enough
Trust Email helps Keycloak trust Google’s verified email claim.
But it does not automatically skip:
Confirm link existing account
You must disable that execution in the custom first broker login flow.
Gotcha 5: Force vs Import Sync Mode
Use Force if Google should keep updating profile data.
Use Import if Keycloak should import once and then local Keycloak data should win.
Neither one replaces proper auto-link flow configuration.
How to Verify the Fix
Always test in an incognito window.
Cached Keycloak sessions can make you think the fix worked when you are just reusing an old session.
Checklist
- Open incognito/private browser.
- Start Laravel login with Google.
- Confirm no email verification link is sent.
- Confirm no “Account already exists” screen appears.
- Confirm no password reset screen appears.
- Confirm user lands back in Laravel successfully.
- In Keycloak Admin, open:
Users → <email> → Identity Provider Links
You should see:
google
linked to the user.
Check Keycloak Logs
Tail the logs while testing.
For systemd:
journalctl -u keycloak -f
Look for success:
SUCCESS: type="IDENTITY_PROVIDER_LOGIN"
Watch for failure:
FAILURE: type="IDENTITY_PROVIDER_FIRST_LOGIN_ERROR"
If you see first login errors, re-check the flow tree.
Most failures come from the Automatically set existing user execution being in the wrong place.
Conclusion
The fix was not in Laravel. Laravel was only starting the login journey. The real fix was in Keycloak’s Google Identity Provider settings, the duplicated first broker login flow, and old required actions left behind during migration.
Once we enabled Trust Email, added Automatically set existing user inside Handle Existing Account, disabled confirm-link verification, and cleaned UPDATE_PASSWORD, the login became normal again.
Google users logged in with Google. Existing users auto-linked. No activation email. No password reset loop. No weird “account already exists” wall.
If you are fighting the same Keycloak Google login activation email issue, start with the flow tree. That is usually where the ghost is hiding.
FAQ
1. Why does Keycloak send an activation email after Google login?
Because the default first broker login flow may still verify existing accounts by email. If Google Trust Email is off, Keycloak does not fully trust Google’s verified email claim.
2. How do I auto-link an existing user in Keycloak Google login?
Duplicate the first broker login flow, add Automatically set existing user inside Handle Existing Account, disable confirm-link verification, and bind the custom flow to the Google IdP.
3. Where should idp-auto-link be added in Keycloak?
It must be added inside:
User creation or linking → Handle Existing Account
Do not add it at the root level.
4. Why am I getting “There is no existing duplicated user identified in ClientSession”?
Because Automatically set existing user is running outside the context where Keycloak has identified the duplicate user. Move it inside Handle Existing Account.
5. Does Trust Email skip the Account Already Exists page?
No. Trust Email only tells Keycloak to trust the IdP’s verified email claim. To skip the account confirmation page, disable Confirm link existing account in the first broker login flow.
6. Why does Keycloak still ask Google users to update password?
Because the user already has UPDATE_PASSWORD in user_required_action. Disable the required action for the realm and clean existing pending actions from the database.
7. Should I use Sync Mode Force or Import for Google?
Use Force if Google should keep updating user profile data. Use Import if you only want to import profile data once and then manage it locally in Keycloak
