- Published on
Keycloak Identity Brokering — From Social Login to External IdP Federation
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction
- Identity Brokering Architecture
- Configuring Social Login — Google, GitHub, Apple
- Brokering External SAML / OIDC IdPs
- Account Linking Strategy — Handling Duplicate Emails
- Attribute and Claim Mapping
- Broker Token Storage and the Stored Tokens API
- Identity Brokering API v2 in Keycloak 26 (Preview)
- Enterprise Scenario — B2B SSO with Customer IdPs
- Custom First Broker Login Flow
- Troubleshooting
- Operational Best Practices
- Closing Thoughts
- References
Introduction
"Add Google login to our service" and "Set up SSO with our customer Azure AD" sound like completely different jobs, but in Keycloak both are solved by a single mechanism: Identity Brokering. Keycloak acts as the IdP toward your applications while simultaneously becoming a client (SP/RP) toward external IdPs, brokering authentication between the two.
The 2026 authentication landscape only increases the value of this capability. Even as passkeys become the default, the standard requirement for B2B SaaS remains "federate with the customer IdP", and the Keycloak 26 line keeps improving Organizations and Identity Brokering, treating this scenario as a first-class citizen. In particular, the Identity Brokering API v2 that appeared as a preview in 26.x standardizes broker extension points that teams have long worked around with custom SPIs.
This article covers, in order: the brokering architecture and how the first login flow works, configuring the big three social providers (Google/GitHub/Apple), external SAML/OIDC IdP federation, account linking strategies, token storage, B2B scenarios, and troubleshooting.
Identity Brokering Architecture
Let us start with the overall flow as a diagram.
+-----------+ +---------------------------+ +-------------+
| Browser | | Keycloak (Broker) | | External IdP|
| | | realm: myrealm | | Google/SAML |
+-----+-----+ +------------+--------------+ +------+------+
| 1. access app, login req | |
|--------------------------->| |
| 2. login page | |
| (list of IdP buttons) | |
|<---------------------------| |
| 3. "Sign in with Google" | |
|--------------------------->| |
| 4. redirect with AuthN request (OIDC/SAML) |
|------------------------------------------------------------>
| 5. authenticate at the external IdP |
<------------------------------------------------------------|
| 6. callback (code/assertion) |
|--------------------------->| |
| | 7. token exchange, verify |
| | external identity, map |
| | 8. run First Login Flow |
| 9. tokens issued by | (new/existing user) |
| myrealm | |
|<---------------------------| |
The key concepts:
- An identity authenticated at an external IdP is represented inside Keycloak as a federated identity and linked to a local user of the realm.
- When a federated identity is seen for the first time, the First Broker Login Flow runs and decides between creating a local user or linking to an existing account.
- Applications know nothing about external IdPs. They only ever receive tokens issued by Keycloak. Adding or swapping IdPs requires zero app code changes — the biggest advantage of brokering.
Configuring Social Login — Google, GitHub, Apple
All three providers are added under the Identity Providers menu of the admin console, but for configuration-as-code we use kcadm here. The common prerequisites are the client id and secret issued by each developer console, plus the Keycloak redirect URI, which always has the following form.
https://kc.example.com/realms/myrealm/broker/IDP-ALIAS/endpoint
Create an OAuth client in the Google Cloud Console, register the redirect URI above, and then create the provider.
kcadm.sh create identity-provider/instances -r myrealm \
-s alias=google \
-s providerId=google \
-s enabled=true \
-s 'config.clientId=GOOGLE_CLIENT_ID' \
-s 'config.clientSecret=GOOGLE_CLIENT_SECRET' \
-s 'config.defaultScope=openid profile email' \
-s 'config.hostedDomain=example.com'
hostedDomain restricts which accounts may log in to a Google Workspace organization domain. For internal services this is a must. However, since this value is also a client-side hint, it is safer to additionally enable Validate hosted domain so that Keycloak verifies the hd claim of the ID token.
GitHub
GitHub is a pure OAuth2 provider rather than OIDC, so there is no ID token; Keycloak calls the user API to fetch the profile.
kcadm.sh create identity-provider/instances -r myrealm \
-s alias=github \
-s providerId=github \
-s enabled=true \
-s 'config.clientId=GITHUB_CLIENT_ID' \
-s 'config.clientSecret=GITHUB_CLIENT_SECRET' \
-s 'config.defaultScope=read:user user:email'
Watch out for users with private email settings. Without the user:email scope, the email arrives as null and the first login flow stalls at the email input screen. GitHub also reports whether an email is verified; allowing account linking with an unverified email becomes an account takeover vector. The linking section below covers this in detail.
Apple
Sign in with Apple is the trickiest of the three. Instead of a fixed client secret it uses a JWT signed with a p8 private key as the client secret, valid for at most six months, so periodic renewal is required.
kcadm.sh create identity-provider/instances -r myrealm \
-s alias=apple \
-s providerId=apple \
-s enabled=true \
-s 'config.clientId=com.example.service' \
-s 'config.teamId=APPLE_TEAM_ID' \
-s 'config.keyId=APPLE_KEY_ID' \
-s 'config.p8Content=P8_PRIVATE_KEY_CONTENT'
Two Apple-specific traps. First, the user name and email are delivered only once, at first authorization. If the first login flow fails and you retry, the name stays empty forever — you must reset the app permission for Sign in with Apple in the Apple developer console and test again. Second, if the user chooses Hide My Email, a relay address under the privaterelay.appleid.com domain comes in. Email-based account linking loses its meaning in this case, so it has to be considered as a policy matter.
Brokering External SAML / OIDC IdPs
This is the enterprise-customer case. An OIDC IdP is simplest when a discovery URL is available.
# OIDC IdP (example: customer Entra ID)
kcadm.sh create identity-provider/instances -r myrealm \
-s alias=customer-a-oidc \
-s providerId=oidc \
-s enabled=true \
-s 'config.useJwksUrl=true' \
-s 'config.issuer=https://login.microsoftonline.com/TENANT_ID/v2.0' \
-s 'config.authorizationUrl=https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize' \
-s 'config.tokenUrl=https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token' \
-s 'config.clientId=ENTRA_APP_ID' \
-s 'config.clientSecret=ENTRA_SECRET' \
-s 'config.defaultScope=openid profile email' \
-s 'config.validateSignature=true'
For a SAML IdP, importing the metadata XML is the canonical approach. When configuring manually, the key settings are these.
kcadm.sh create identity-provider/instances -r myrealm \
-s alias=customer-b-saml \
-s providerId=saml \
-s enabled=true \
-s 'config.entityId=https://kc.example.com/realms/myrealm' \
-s 'config.singleSignOnServiceUrl=https://idp.customer-b.com/sso/saml' \
-s 'config.nameIDPolicyFormat=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' \
-s 'config.validateSignature=true' \
-s 'config.wantAssertionsSigned=true' \
-s 'config.signatureAlgorithm=RSA_SHA256' \
-s 'config.postBindingResponse=true'
The non-negotiable security items in SAML federation are validateSignature and wantAssertionsSigned. SAML without signature validation is defenseless against assertion forgery. Certificate expiry is also a recurring outage cause, so put the IdP certificate expiry date on your monitoring list.
Account Linking Strategy — Handling Duplicate Emails
This is where policy decisions are hardest in brokering operations. The default First Broker Login Flow works in this order.
federated identity arrives for the first time
|
v
+-------------------------------+
| Review Profile (default: on) | user reviews/edits the profile
+-------------------------------+
|
v
+-------------------------------+
| Create User If Unique | if email/username is unique,
+-------------------------------+ create a new local user
| (duplicate found)
v
+-------------------------------+
| Handle Existing Account |
| - Confirm link existing | "Link to the existing account?"
| - Verify via email | send an email verification link
| - or re-authenticate | re-auth with the existing password
+-------------------------------+
There are broadly three policy options.
| Strategy | Behavior | Suitable for |
|---|---|---|
| Auto link | Always link on email match | Internal only, all IdPs trusted |
| Link after verification | Link after email verification or re-auth | Sensible B2C default |
| No linking | Error on duplicate, manual handling | Regulated, high-security |
There is one security principle you must never forget here: never auto-link based on an unverified email. An attacker can register an external IdP account (at an IdP that does not enforce email verification) using the victim email, log in, get linked to the existing account of the victim, and take it over. Only enable the Trust Email option when you are confident the IdP guarantees email verification.
The scenario where an existing user links a social account from their settings screen is handled not by the first login flow but by the Linked Accounts feature of the Account Console, or an idp_link flow similar to AIA (Application Initiated Action). When constructing the linking URL directly from an application, build it with the nonce and hash required by the client-initiated account linking specification.
Attribute and Claim Mapping
Reflecting the claims and attributes sent by an external IdP onto local users is the job of Identity Provider Mappers. Where the Protocol Mappers from the first article shape the outgoing token, IdP mappers transform the incoming identity.
# Map the department claim of the OIDC IdP to a user attribute
kcadm.sh create identity-provider/instances/customer-a-oidc/mappers -r myrealm \
-s name=dept-import \
-s identityProviderAlias=customer-a-oidc \
-s identityProviderMapper=oidc-user-attribute-idp-mapper \
-s 'config.claim=department' \
-s 'config."user.attribute"=department' \
-s 'config.syncMode=FORCE'
# Map a SAML attribute to a realm role
kcadm.sh create identity-provider/instances/customer-b-saml/mappers -r myrealm \
-s name=admin-role-mapper \
-s identityProviderAlias=customer-b-saml \
-s identityProviderMapper=saml-role-idp-mapper \
-s 'config."attribute.name"=memberOf' \
-s 'config."attribute.value"=CN=ServiceAdmins' \
-s 'config.role=platform-admin' \
-s 'config.syncMode=FORCE'
The most important setting is syncMode.
- IMPORT: applied once at first login. Later changes on the IdP side are ignored.
- FORCE: overwritten with the IdP values on every login.
- LEGACY: backwards-compatible behavior (not recommended).
The usual dividing line: data for which the IdP is the source of truth — organization info, roles — uses FORCE, while profiles the user may edit directly in Keycloak use IMPORT. When granting roles through a FORCE mapping, you must also test that a user whose privilege was revoked at the IdP loses the role on their next login.
Broker Token Storage and the Stored Tokens API
Enabling the Store Tokens option in the IdP settings makes Keycloak persist the external IdP access and refresh tokens. The use case is clear: calling the external IdP API on behalf of the user. For a feature that reads the repository list of a GitHub-logged-in user, the application fetches the GitHub access token from the broker token retrieval endpoint.
GET /realms/myrealm/broker/github/token HTTP/1.1
Host: kc.example.com
Authorization: Bearer KEYCLOAK_ACCESS_TOKEN
Two conditions must hold for this call to succeed.
- Store Tokens and Stored Tokens Readable enabled in the IdP settings
- The calling user holds the read-token role of the broker client
kcadm.sh add-roles -r myrealm \
--uusername alice \
--cclientid broker \
--rolename read-token
The operational caveat is the lifetime of the stored external tokens. Keycloak attempts automatic refresh with the external refresh token, but if the external IdP revokes the token, the 401 propagates as-is. Applications must implement a re-authorization path (re-login through that IdP) for when stored token calls fail.
Identity Brokering API v2 in Keycloak 26 (Preview)
Keycloak 26.x introduces a redesigned v2 API for the internals of Identity Brokering as a preview. Until now, brokering extensions had to be implemented by extending AbstractIdentityProvider and wiring scattered callbacks, so custom IdP integrations (national identity verification providers, legacy in-house auth gateways) tended to break on every version upgrade. The v2 direction separates the extension points — building the authentication request, processing the response, transforming the identity, and deciding on linking — into more explicit contracts. As a preview it is too early for production, but teams maintaining custom brokering code should start planning their migration now. See the official release notes for details.
Enterprise Scenario — B2B SSO with Customer IdPs
Design points for solving the "every customer wants SSO through their own IdP" requirement of B2B SaaS with brokering.
+--------------------------------------+
| Keycloak realm: saas |
Customer A users --> | IdP: customer-a-oidc (Entra ID) |
Customer B users --> | IdP: customer-b-saml (Okta) |
Direct users --> | Local username/password + passkey |
| |
| Domain mapping via Organizations |
| a-corp.com -> customer-a-oidc |
| b-inc.com -> customer-b-saml |
+------------------+-------------------+
|
v
SaaS Application (single client)
- Home IdP Discovery: you cannot list dozens of IdP buttons on the login screen, so route to the IdP automatically by email domain. The Keycloak Organizations feature provides domain-to-IdP mapping and membership management, and on 26.x it should be your first consideration.
- kc_idp_hint: used to skip the login screen and send users straight to a specific IdP from a customer-dedicated entry URL.
https://kc.example.com/realms/saas/protocol/openid-connect/auth
?client_id=saas-app
&response_type=code
&scope=openid
&redirect_uri=https://app.example.com/callback
&kc_idp_hint=customer-a-oidc
- Tenant isolation vs a single realm: a realm per customer gives perfect isolation, but operational and memory costs grow with realm count. A single realm plus many IdPs plus Organizations is the mainstream 2026 pattern, with a pragmatic hybrid that moves only regulation-bound customers into dedicated realms.
- Onboarding automation: adding customer IdPs is repetitive work, so template it with the Admin REST API or Terraform. The goal is for new customer onboarding to become self-service configuration rather than ticket processing.
Custom First Broker Login Flow
The canonical approach is to duplicate the built-in flow and adapt it. Three frequent variations:
- Disable Review Profile: if IdP data is trustworthy, skip the user confirmation screen to reduce friction. Set the Review Profile execution to OFF.
- Auto-link trusted IdPs: for IdPs with guaranteed email verification, such as the corporate IdP, place an executor like Automatically Set Existing User under Handle Existing Account to link without a confirmation step. Never apply this to external social IdPs.
- Collect additional data: insert steps such as terms consent or department selection as a custom Required Action or custom authenticator.
# Duplicate the default flow and assign it to the IdP
kcadm.sh create authentication/flows/first%20broker%20login/copy -r myrealm \
-s newName=corp-first-login
kcadm.sh update identity-provider/instances/customer-a-oidc -r myrealm \
-s 'config.firstBrokerLoginFlowAlias=corp-first-login'
After changing a flow, always regression-test all three cases — new user, existing user, duplicate email — in a private browser window. Flow misconfiguration is an area that translates directly into login failure for every user.
Troubleshooting
The brokering failures most often seen in the field, by symptom.
| Symptom | Cause | Fix |
|---|---|---|
| invalid_redirect_uri | Broker endpoint not registered at the external IdP | Register the exact redirect URI in the IdP console |
| Page Expired | Back button/retry during the first login flow | Re-enter the flow; for Apple, reset permission and retry |
| Stuck creating a user without email | GitHub private email, missing scope | Add the user:email scope |
| Login loop | Session/prompt conflicts between broker and IdP | Check the prompt parameter and IdP session policy |
| SAML Invalid signature | IdP certificate rotated, metadata mismatch | Re-import metadata, monitor certificate expiry |
| Same person ends up with 2 accounts | No linking policy, new account created | Merge duplicates, then fix the linking flow |
| IdP session survives logout | Logout not propagated to the external IdP | Check backchannel/front-channel logout support in IdP settings |
Two debugging tools matter most. First, Events in the admin console (filter Login Events by types such as IDENTITY_PROVIDER_LOGIN and FIRST_BROKER_LOGIN). Second, adjusting the server log level.
kc.sh start --log-level=INFO,org.keycloak.broker:DEBUG,org.keycloak.saml:DEBUG
For SAML problems, the fastest route is capturing the raw assertion with a browser extension (SAML-tracer) and inspecting the NameID format and attribute names directly.
Operational Best Practices
A brokering setup is harder to maintain than to build. Here are the items to take care of in the operations phase.
Lifecycle Management of Credentials and Certificates
Every secret exchanged with external IdPs expires. Memorizing the symptom that appears at expiry speeds up incident response.
| Item | Typical lifetime | Symptom at expiry |
|---|---|---|
| Apple client secret (signed JWT) | Up to 6 months | invalid_client error |
| Social IdP client secret | Per provider policy | unauthorized_client error |
| SAML IdP signing certificate | 1 to 3 years | Invalid signature error |
| JWKS keys of external OIDC IdPs | Rotated at any time | Transient token validation failures |
For items with predictable renewal such as the Apple secret, automating via a CI schedule is the safe route.
# Run monthly in CI: generate a new client secret (JWT) from the p8 key and apply it
NEW_SECRET=$(python3 generate_apple_secret.py \
--team APPLE_TEAM_ID --key-id APPLE_KEY_ID \
--key-file AuthKey.p8 --client-id com.example.service)
kcadm.sh update identity-provider/instances/apple -r myrealm \
-s "config.clientSecret=$NEW_SECRET"
Ship Events to Your SIEM
Brokering events are the primary signal for security monitoring. Enable event persistence and include types such as IDENTITY_PROVIDER_LOGIN and FEDERATED_IDENTITY_LINK in the collection targets.
kcadm.sh update events/config -r myrealm \
-s eventsEnabled=true \
-s eventsExpiration=2592000 \
-s 'enabledEventTypes=["LOGIN","LOGIN_ERROR","IDENTITY_PROVIDER_LOGIN","IDENTITY_PROVIDER_FIRST_LOGIN","IDENTITY_PROVIDER_LINK_ACCOUNT","FEDERATED_IDENTITY_LINK","REMOVE_FEDERATED_IDENTITY"]'
In particular, a spike in FEDERATED_IDENTITY_LINK events can indicate an attack probing the gaps in your linking policy, so set an alert rule on it. Streaming directly to Kafka or syslog through a custom event listener SPI is also a common setup.
A Periodic Review Checklist
- Quarterly, disable or remove unused IdPs (zero recent login events).
- Dashboard the login success rate per IdP to detect quality degradation of a specific customer IdP early.
- After any change to the first broker login flow, rerun E2E tests for the three cases: new user, existing user, duplicate email.
- Poll the metadata URLs of customer IdPs periodically to absorb certificate rotation automatically.
- Back up realm exports regularly to preserve the shape of IdP configuration.
- For IdPs with Store Tokens enabled, sample-check the validity of stored tokens periodically and verify the re-authorization flow works on failure.
- Before any Keycloak version upgrade, validate the compatibility of custom first broker login flows and brokering-related SPIs in staging first.
Closing Thoughts
Identity Brokering starts out looking like a small feature — "add social login" — but ends up underpinning your entire B2B SSO and account lifecycle policy. Key takeaways:
- Applications look only at Keycloak; adding or replacing IdPs is absorbed by broker configuration.
- Each of the big three social providers has its own traps to internalize: Google hd validation, the GitHub email scope, the one-shot Apple profile and secret renewal.
- Account linking is a security decision. Auto-linking on unverified emails is an account takeover vector.
- Separate IdP mapper syncMode into IMPORT and FORCE according to data ownership.
- For B2B, single realm + Organizations + domain routing is the base pattern; teams with custom brokering code should prepare for the API v2 transition.
References
- Keycloak Server Administration Guide — Integrating Identity Providers
- Keycloak Documentation
- Keycloak Release Notes
- Keycloak 26.6.0 Released
- OpenID Connect Core 1.0
- SAML 2.0 Core Specification
- RFC 6749 — The OAuth 2.0 Authorization Framework
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
- Sign in with Apple Documentation
- GitHub OAuth Apps Documentation
- Google Identity — OpenID Connect
- OAuth 2.1 draft