Skip to main content

Skillber v1.0 is here!

Learn more

Keycloak SSO Configuration — OIDC, SAML & User Federation

Checking access...

Your Keycloak IdP is deployed with users, roles, and a realm. Now it is time to make it useful — register applications as clients, configure Single Sign-On, federate users from an LDAP directory, and harden authentication with MFA policies.

Prerequisites

Complete the Keycloak Lab Deployment guide first. Keycloak must be running at http://localhost:8080 with the sandbox realm created and users alice and bob configured.

What You Will Learn

  1. Register an OIDC client application (confidential + public)
  2. Test the OIDC Authorization Code flow end-to-end
  3. Register a SAML client application
  4. Configure user federation with LDAP (OpenLDAP)
  5. Enable and test multi-factor authentication
  6. Configure authentication flows and policies

Understanding Keycloak Clients

A Client in Keycloak represents an application or service that uses Keycloak for authentication. Each client has its own configuration — protocol (OIDC or SAML), access type, redirect URIs, and security settings.

Client Types

Client TypeProtocolUse CaseExample
ConfidentialOIDCServer-side apps with backendSpring Boot, Node.js, Django
PublicOIDCBrowser-only apps (no backend)React, Vue, Angular SPA
Bearer-onlyOIDCAPI services that validate tokensREST API gateway
SAML EntitySAML 2.0Enterprise SAML SP integrationSalesforce, Workday, legacy apps

How OIDC Clients Work

User's Browser Keycloak (IdP) Client App (Web App)
│ │ │
│ 1. Click "Login" │ │
│───────────────────────────►│ │
│ │ │
│ 2. Redirect to Keycloak │ │
│◄───────────────────────────│ │
│ │ │
│ 3. User enters credentials│ │
│───────────────────────────►│ │
│ │ │
│ 4. Auth code returned │ │
│◄───────────────────────────│ │
│ │ │
│ 5. Browser redirects to │ │
│ app with auth code │───────────────────────────►│
│ │ │
│ │ 6. App sends code + │
│ │ client secret to │
│ │ Keycloak for tokens │
│ │◄───────────────────────────│
│ │ │
│ │ 7. Keycloak returns │
│ │ access_token + │
│ │ id_token + │
│ │ refresh_token │
│ │───────────────────────────►│
│ │ │
│ 8. App loads (user is │ │
│ logged in) │ │
│◄───────────────────────────│ │

Registering an OIDC Confidential Client

  1. Open the Keycloak Admin Console: http://localhost:8080/admin
  2. Ensure you are in the sandbox realm (check the top-left dropdown)
  3. Click Clients in the sidebar
  4. Click Create client

Configure Client — General Settings

FieldValueExplanation
Client IDweb-appUnique identifier for this client
NameWeb Application (Lab)Human-readable display name
DescriptionDemo web app for SSO testingPurpose documentation
Always display in consoleONShow in the Account Console for users

Click Next.

Configure Client — Capability Config

SettingValueExplanation
Client authenticationONConfidential client — requires client secret
AuthorizationOFFEnable for fine-grained authorization (advanced)
Standard flowONAuthorization Code flow (recommended for web apps)
Direct access grantsOFFDisable password grant (more secure)
Implicit flowOFFDeprecated — do not use
Service accounts rolesOFFEnable for machine-to-machine auth
OIDC CIBA grantOFFConsumer-to-business auth (advanced)

Click Next.

Configure Client — Login Settings

SettingValueExplanation
Root URLhttp://localhost:3000Base URL of the application
Home URLhttp://localhost:3000Landing page after login
Valid redirect URIshttp://localhost:3000/*Critical — only these URIs can receive auth codes
Valid post logout redirect URIshttp://localhost:3000/*Where to redirect after logout
Web originshttp://localhost:3000Allowed CORS origins (for JavaScript)

Redirect URI Security

Redirect URIs are a critical security control. An attacker can steal auth codes if redirect URIs are too permissive. Best practices:

  • Use the most specific URI possible (e.g., https://app.example.com/callback not https://app.example.com/*)
  • Never use * as a wildcard alone — always prefix with a path
  • Never use http://localhost in production (use HTTPS)
  • Validate redirect URIs during security reviews

Click Save.

Retrieve Client Secret

After saving, the client configuration page opens:

  1. Go to the Credentials tab
  2. You will see the Client secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  3. Copy this secret — you need it when configuring your application

Client Secret Handling

The client secret is a credential — treat it like a password:

  • Never hardcode it in source code
  • Use environment variables or a secrets manager
  • Rotate secrets periodically
  • Monitor for secret exposure

Configure Client Roles

  1. Go to the Roles tab within the client configuration
  2. Click Create role
  3. Role name: admin
  4. Description: Web app administrator
  5. Click Save
  6. Repeat: Create role editor with description Content editor

Test the OIDC Flow

Keycloak provides a built-in test endpoint to verify the client configuration:

Terminal window
# Step 1: Get the authorization URL (open in browser)
echo "http://localhost:8080/realms/sandbox/protocol/openid-connect/auth?
client_id=web-app&
redirect_uri=http://localhost:3000/callback&
response_type=code&
scope=openid&
state=test123"
# Open this URL in a browser — it will redirect to Keycloak's login page
  1. Open the URL constructed above in a browser
  2. Log in as alice / Alice@Lab2026
  3. Keycloak will redirect to http://localhost:3000/callback?code=... (the redirect will fail since there is no app on port 3000, but you can see the auth code in the URL)
Terminal window
# Step 2: Exchange the auth code for tokens (using curl)
# Replace {AUTH_CODE} with the code from the redirect URL
AUTH_CODE="<paste-code-here>"
CLIENT_SECRET="<paste-client-secret-here>"
curl -s -X POST http://localhost:8080/realms/sandbox/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=web-app" \
-d "client_secret=$CLIENT_SECRET" \
-d "code=$AUTH_CODE" \
-d "redirect_uri=http://localhost:3000/callback" | jq

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ...",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ...",
"not-before-policy": 0,
"session_state": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"scope": "openid email profile"
}

Decode the Access Token

Terminal window
# Decode the JWT (copy-paste your access_token)
# A JWT has three base64-encoded parts separated by dots
# Part 1: Header, Part 2: Payload, Part 3: Signature
echo "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ..." | \
cut -d. -f2 | \
base64 -d 2>/dev/null || true | \
jq

The decoded payload shows:

{
"exp": 1718541234,
"iat": 1718540934,
"auth_time": 1718540934,
"jti": "abcdef01-2345-6789-abcd-ef0123456789",
"iss": "http://localhost:8080/realms/sandbox",
"aud": ["web-app", "account"],
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"typ": "Bearer",
"azp": "web-app",
"session_state": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"acr": "1",
"allowed-origins": ["http://localhost:3000"],
"realm_access": {
"roles": ["app-admin", "offline_access", "uma_authorization"]
},
"resource_access": {
"account": {
"roles": ["manage-account", "manage-account-links", "view-profile"]
}
},
"scope": "openid email profile",
"sid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email_verified": true,
"name": "Alice Johnson",
"preferred_username": "alice",
"given_name": "Alice",
"family_name": "Johnson",
"email": "alice@example.com"
}

This confirms the OIDC flow is working correctly — Keycloak authenticated Alice, included her profile information in the ID token, and issued an access token with role information.

Registering a Public OIDC Client (SPA)

Single Page Applications (React, Vue, Angular) cannot securely store a client secret. Use a public client instead:

Create the Client

  1. Go to ClientsCreate client
  2. Client ID: spa-app
  3. Name: Single Page App (Lab)
  4. Click Next
  5. Client authentication: OFF (public client)
  6. Standard flow: ON
  7. Direct access grants: OFF
  8. Click Next
  9. Valid redirect URIs: http://localhost:5173/*
  10. Web origins: http://localhost:5173
  11. Click Save

Public Client vs Confidential Client

AspectConfidentialPublic
SecretYes (shared secret)No
Code flowAuthorization Code + PKCEAuthorization Code + PKCE (required)
SecuritySecret can be stolen from serverNo secret to steal
Use caseServer-rendered web appsSPAs, mobile apps
Token storageBackend sessionBrowser memory (never localStorage)

Always Use PKCE for Public Clients

PKCE (Proof Key for Code Exchange) is mandatory for public clients and recommended for all OIDC flows. It prevents authorization code interception attacks. Keycloak supports PKCE out of the box — most OIDC libraries implement it automatically.

Registering a SAML Client

Many enterprise applications (Salesforce, Workday, Tableau, ServiceNow) use SAML 2.0 for SSO. Here is how to configure a SAML client in Keycloak.

Create the Client

  1. Go to ClientsCreate client
  2. Client ID: saml-app
  3. Name: SAML Application (Lab)
  4. Click Next
  5. Client authentication: ON (SAML entities are always confidential)
  6. Toggle to SAML protocol (click the protocol dropdown and select SAML)
  7. Capability config appears — configure:
SettingValueExplanation
SAML protocolSelectedSwitch from OIDC to SAML
  1. Click Next

Configure SAML Settings

SettingValueExplanation
Root URL(leave empty)Not used for SAML
Valid Redirect URIs(SAML uses ACS URL instead — see below)
Home URL(leave empty)Not used for SAML

Click Save.

Configure SAML-Specific Settings

After saving, go to the Settings tab and configure:

SectionSettingValueExplanation
SAML CapabilitiesAssertion Consumer Service POST Binding URLhttp://localhost:8081/saml/callbackWhere the SP receives SAML assertions
Assertion Consumer Service Redirect Binding URL(leave empty)Use POST binding for production
Logout Service POST Binding URLhttp://localhost:8081/saml/logoutSLO endpoint
Logout Service Redirect Binding URL(leave empty)Redirect binding for logout
SAML GeneralEncrypt AssertionsONEncrypt SAML assertions
Client Signature RequiredONRequire signed authn requests
Force POST BindingONAlways use POST (more secure)
Include Authn StatementONInclude authentication context
Sign DocumentsONSign SAML documents
Optimize LookupONCache for performance
SAML AssertionsSignature AlgorithmRSA-SHA256Modern, secure
SAML Signature Key NameKEY_IDUse Key ID in signature
Canonicalization Methodhttp://www.w3.org/2001/10/xml-exc-c14n#Standard

SAML vs OIDC — Protocol Decision Guide

ScenarioUse
Modern web/mobile appOIDC — lighter, JSON-based, better developer experience
Enterprise SaaS (Salesforce, Workday, ServiceNow)SAML 2.0 — most enterprise SPs only support SAML
Government/education (often mandated)SAML 2.0 — many government SSO mandates reference SAML
API/microservice authOIDC — designed for API access with access tokens
Mobile app authOIDC — native OIDC SDKs, PKCE support

Export SAML Metadata

SAML service providers need IdP metadata to establish trust:

  1. In the client page, go to the Action menu (top-right)
  2. Click Download adapter config
  3. Select SAML Metadata format
  4. The metadata XML contains:
    • IdP entity ID
    • SSO endpoint URL
    • Logout endpoint URL
    • Signing certificate (public key)
  5. This XML is uploaded to the SAML SP (e.g., Salesforce) to establish the trust relationship

The metadata looks like:

<EntityDescriptor entityID="http://localhost:8080/realms/sandbox"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo>
<X509Data>
<X509Certificate>MIIC... (base64 cert)</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/realms/sandbox/protocol/saml"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/realms/sandbox/protocol/saml"/>
</IDPSSODescriptor>
</EntityDescriptor>

Configuring User Federation with LDAP

User Federation synchronises users from an external directory (Active Directory, OpenLDAP) into Keycloak. This is the most common enterprise integration pattern — users are managed in AD, and Keycloak references them without duplicating password storage.

Architecture

Active Directory / OpenLDAP Keycloak
┌──────────────────────┐ ┌──────────────────────┐
│ Users: 5000 │ │ Realm: sandbox │
│ Groups: 200 │◄───Sync────►│ │ │
│ OUs: HR, IT, Sales │ (LDAP) │ ├─ Users (read from │
│ │ │ │ LDAP, cached) │
│ Password storage: │ │ ├─ Groups (mapped │
│ AD / OpenLDAP │ │ │ from AD groups) │
└──────────────────────┘ │ └─ Auth: LDAP bind │
│ or Kerberos │
│ │
│ Users authenticate │
│ against LDAP │
│ (passwords never │
│ stored in Keycloak) │
└──────────────────────┘

Setting Up an OpenLDAP Test Server

For the lab, deploy a lightweight OpenLDAP server alongside Keycloak:

Add OpenLDAP to Docker Compose

Edit ~/keycloak-lab/docker-compose.yml and add:

openldap:
image: osixia/openldap:1.5.0
container_name: keycloak-ldap
environment:
LDAP_ORGANISATION: "Lab Corp"
LDAP_DOMAIN: "lab.corp"
LDAP_ADMIN_PASSWORD: "Ldap@dmin2026"
LDAP_BASE_DN: "dc=lab,dc=corp"
ports:
- "389:389"
- "636:636"
volumes:
- ldap_data:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
networks:
- keycloak-network
restart: unless-stopped
phpldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: keycloak-ldap-admin
environment:
PHPLDAPADMIN_LDAP_HOSTS: openldap
PHPLDAPADMIN_HTTPS: "false"
ports:
- "8090:80"
networks:
- keycloak-network
depends_on:
- openldap
restart: unless-stopped
volumes:
ldap_data:
ldap_config:

Then restart:

Terminal window
cd ~/keycloak-lab
docker compose up -d

Add Test Users to LDAP

Access the LDAP admin interface at http://localhost:8090

  • Login DN: cn=admin,dc=lab,dc=corp
  • Password: Ldap@dmin2026

Or use LDIF files via the command line:

Terminal window
# Create an LDIF file with test users
cat > test-users.ldif << 'EOF'
dn: ou=people,dc=lab,dc=corp
objectClass: organizationalUnit
ou: people
dn: cn=John Doe,ou=people,dc=lab,dc=corp
objectClass: inetOrgPerson
cn: John Doe
sn: Doe
uid: jdoe
mail: jdoe@lab.corp
userPassword: John@Ldap2026
dn: cn=Jane Smith,ou=people,dc=lab,dc=corp
objectClass: inetOrgPerson
cn: Jane Smith
sn: Smith
uid: jsmith
mail: jsmith@lab.corp
userPassword: Jane@Ldap2026
dn: ou=groups,dc=lab,dc=corp
objectClass: organizationalUnit
ou: groups
dn: cn=engineering,ou=groups,dc=lab,dc=corp
objectClass: groupOfNames
cn: engineering
member: cn=John Doe,ou=people,dc=lab,dc=corp
member: cn=Jane Smith,ou=people,dc=lab,dc=corp
EOF
# Import into OpenLDAP
docker compose exec openldap ldapadd -x -D "cn=admin,dc=lab,dc=corp" \
-w "Ldap@dmin2026" -f test-users.ldif -H ldap://localhost

Configure User Federation in Keycloak

  1. In Keycloak Admin Console → sandbox realm
  2. Go to User Federation in the sidebar
  3. Click Add providerldap
  4. Configure:
SettingValueExplanation
Console display nameLab OpenLDAPDisplay name
VendorOtherOpenLDAP is not Active Directory
Connection URLldap://openldap:389Docker service name (internal network)
Bind DNcn=admin,dc=lab,dc=corpLDAP admin user
Bind CredentialLdap@dmin2026LDAP admin password
Authentication TypesimplePassword-based bind
  1. Click Test connection — you should see a success message

  2. Configure LDAP Searching and Updating:

SettingValueExplanation
Users DNou=people,dc=lab,dc=corpWhere to search for users
User Object ClassesinetOrgPerson, organizationalPersonLDAP object class for users
Username LDAP AttributeuidMaps to Keycloak username
RDN LDAP AttributeuidRelative DN
UUID LDAP AttributeentryUUIDUnique identifier
User Email AttributemailEmail mapping
User First Name AttributegivenName(not in our LDIF, leave for now)
User Last Name AttributesnLast name mapping
Read OnlyONKeycloak reads from LDAP, does not write back
Edit ModeREAD_ONLYChanges happen at the LDAP source
Sync RegistrationsOFFUsers register in LDAP, not Keycloak
  1. Click Save

  2. Go to the Mappers tab of the LDAP provider

  3. Click Add mapper → select user-attribute-ldap-mapper

  4. Add a mapper for givenName → first name (since our LDIF has cn but not givenName explicitly):

SettingValue
NamegivenName
LDAP Attributecn
User Model AttributefirstName
Always Read Value From LDAPON
Is Mandatory In LDAPOFF

Click Save.

Sync Users from LDAP

  1. Go to the LDAP provider settings
  2. Click Synchronize all users
  3. The sync will import jdoe and jsmith as Keycloak users
  4. Go to Users — you should see them listed
  5. Unlike Keycloak-native users, LDAP users show:
    • Federation Link: lab-openldap (indicating they came from LDAP)
    • No password can be set in Keycloak (passwords are verified against LDAP)

Test LDAP Authentication

  1. Open a private/incognito browser window
  2. Go to: http://localhost:8080/realms/sandbox/account/
  3. Log in as jdoe with password John@Ldap2026
  4. Authentication succeeds against OpenLDAP

LDAP Sync Strategies

StrategyWhenBehaviour
Synchronize all usersInitial setupImports all LDAP users matching the filter
Synchronize changed usersPeriodic syncImports only users modified since last sync
Periodic sync (on)ProductionKeycloak polls LDAP on a schedule (default: 1 day)
Periodic sync (off)Manual-onlySync only when manually triggered

For production, enable periodic sync with a 4-6 hour interval and also trigger an immediate sync after any mass user change in LDAP.

Configuring Multi-Factor Authentication (MFA)

Keycloak supports multiple MFA methods natively. Configure a TOTP-based MFA policy.

  1. In the sandbox realm, go to Authentication in the sidebar
  2. Click the Policies tab

Configure MFA Policy

SettingValueExplanation
OTP TypetotpTime-based One-Time Password (Google Authenticator, Authy)
OTP AlgorithmSHA1Standard for TOTP compatibility
OTP Digits6Standard 6-digit codes
OTP Token period30Code changes every 30 seconds
Initial Counter2For HOTP only — ignored for TOTP
Number of supported OTPs1Number of concurrent OTP tokens per user

Configure Required MFA for All Users

  1. Go to the Flows tab
  2. Click Duplicate on the browser flow (never modify built-in flows)
  3. Name the copy: browser-with-mfa
  4. Edit the new flow:
StepAction
CookieREQUIRED (existing session check)
OTP FormREQUIRED (always require OTP after password)
KDC Identity (Kerberos)DISABLED (not needed in lab)
  1. Click Bind flow → select Browser Flow → choose browser-with-mfa

Make MFA Required via Authentication Policy

  1. Go to AuthenticationPolicies tab
  2. Click Require OTP under “Required actions for all users”
  3. Click Register

Test MFA

  1. Log out of the admin console
  2. Log back in as alice
  3. You will be prompted to set up MFA:
┌─────────────────────────────────────────────────────────┐
│ Setup Two-Factor Authentication │
│ │
│ 1. Install an authenticator app: │
│ Google Authenticator, Authy, 1Password, etc. │
│ │
│ 2. Scan this QR code with your app: │
│ ┌─────────────────────────────────────────────┐ │
│ │ [QR CODE IMAGE] │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Or manually enter: │
│ Issuer: Keycloak (sandbox) │
│ Account: alice │
│ Secret: JBSWY3DPEHPK3PXP │
│ │
│ 3. Enter the 6-digit code from your app: │
│ [______] │
│ │
│ [Submit] │
└─────────────────────────────────────────────────────────┘
  1. Set up your authenticator app and submit the code
  2. On subsequent logins, you will enter your password then a TOTP code

Configuring Authentication Flows

Keycloak authentication flows define the order and requirement of authentication steps. Flows can be customised per realm.

Built-in Flows

Flow NamePurpose
BrowserInteractive web login (username/password → optional MFA)
Direct grantOAuth2 Resource Owner Password Credentials grant (API-based)
RegistrationUser self-registration flow
Reset credentialsForgot password flow
First broker loginFirst-time login via social/identity provider broker
CLICommand-line (keycloak.css) authentication

Creating a Custom Flow

To add “Passwordless” authentication (skip password, use WebAuthn only):

  1. AuthenticationFlowsDuplicate browser → name it passwordless
  2. Remove the password form step
  3. Add WebAuthn Passwordless as the only REQUIRED step
  4. Bind this flow to a specific client via the client’s Authentication Flow Override setting

Environment Variables for App Configuration

When configuring a real application to use Keycloak, the typical configuration looks like this:

Terminal window
# Keycloak OIDC configuration for a Spring Boot application
KEYCLOAK_AUTH_SERVER_URL=http://localhost:8080
KEYCLOAK_REALM=sandbox
KEYCLOAK_CLIENT_ID=web-app
KEYCLOAK_CLIENT_SECRET=<your-client-secret>
KEYCLOAK_SSL_REQUIRED=external
# OIDC discovery URL (auto-configures endpoints)
# http://localhost:8080/realms/sandbox/.well-known/openid-configuration

The OIDC Discovery URL at http://localhost:8080/realms/sandbox/.well-known/openid-configuration returns all endpoints in JSON format — most OIDC libraries auto-configure from this URL.

Troubleshooting SSO Integration

SymptomCauseSolution
”Invalid redirect URI”Redirect URI does not match client configCheck Valid Redirect URIs in client settings — must match exactly
”Invalid client secret”Wrong secret or client is publicVerify Credentials tab, ensure Client Authentication is ON
”Invalid token”Token expired or malformedAccess tokens expire in 300 seconds (default) — use refresh token
Users can log in but no roles in tokenRoles not mapped to clientAdd “Client roles” or “Realm roles” mapper in Client Scopes
LDAP sync failsConnection or credentialsTest connection button. Check docker compose logs openldap
LDAP users cannot log inWrong password or bind issueVerify LDAP password. Check “Test authentication” in User Federation
SAML ACS errorACS URL mismatchVerify SP’s ACS URL matches Keycloak’s client configuration
Infinite redirect loopSession cookie not setEnsure Keycloak URL is correct, check SameSite cookie settings

Verification Checklist

#TaskStatusVerification
1OIDC confidential client createdClient listed in Clients page with Client Authentication ON
2Client secret retrievedCredentials tab shows secret
3Auth code flow testedSuccessfully exchanged code for tokens
4JWT token decoded with user infoPayload shows Alice’s name, email, roles
5OIDC public client createdClient Authentication OFF, PKCE-capable
6SAML client createdSAML metadata XML can be exported
7OpenLDAP deployed and populatedphpLDAPadmin shows users at http://localhost:8090
8User Federation configuredLDAP connection test succeeds
9LDAP users synced to Keycloakjdoe and jsmith visible in Users
10LDAP authentication worksjdoe can log into Account Console
11TOTP MFA configured and testedLogin requires password + TOTP code

Key Takeaways

  • OIDC clients represent applications — confidential for server-side apps (with secret), public for SPAs/mobile (no secret, PKCE required)
  • The Authorization Code flow is the most secure OIDC flow — the client exchanges a temporary code for tokens, never exposing the token to the browser
  • SAML clients require metadata exchange — Keycloak exports IdP metadata XML that the service provider imports to establish trust
  • User Federation synchronises external directories — LDAP/AD users are referenced by Keycloak but managed in their source directory; passwords never leave the LDAP server
  • Authentication flows are fully customisable — built-in flows cover browser login, direct grants, registration, password reset, and CLI authentication
  • MFA policies are configured at the realm level — OTP, WebAuthn, or custom flows can be required for all users or specific clients

Next Steps

You have now deployed Keycloak, configured OIDC and SAML clients, federated LDAP users, and enabled MFA. The same patterns apply to integrating real applications — the Keycloak documentation provides adapter guides for Spring Boot, Node.js, Python, .NET, and all major frameworks.