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
- Register an OIDC client application (confidential + public)
- Test the OIDC Authorization Code flow end-to-end
- Register a SAML client application
- Configure user federation with LDAP (OpenLDAP)
- Enable and test multi-factor authentication
- 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 Type | Protocol | Use Case | Example |
|---|---|---|---|
| Confidential | OIDC | Server-side apps with backend | Spring Boot, Node.js, Django |
| Public | OIDC | Browser-only apps (no backend) | React, Vue, Angular SPA |
| Bearer-only | OIDC | API services that validate tokens | REST API gateway |
| SAML Entity | SAML 2.0 | Enterprise SAML SP integration | Salesforce, 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
Navigate to Client Registration
- Open the Keycloak Admin Console: http://localhost:8080/admin
- Ensure you are in the sandbox realm (check the top-left dropdown)
- Click Clients in the sidebar
- Click Create client
Configure Client — General Settings
| Field | Value | Explanation |
|---|---|---|
| Client ID | web-app | Unique identifier for this client |
| Name | Web Application (Lab) | Human-readable display name |
| Description | Demo web app for SSO testing | Purpose documentation |
| Always display in console | ON | Show in the Account Console for users |
Click Next.
Configure Client — Capability Config
| Setting | Value | Explanation |
|---|---|---|
| Client authentication | ON | Confidential client — requires client secret |
| Authorization | OFF | Enable for fine-grained authorization (advanced) |
| Standard flow | ON | Authorization Code flow (recommended for web apps) |
| Direct access grants | OFF | Disable password grant (more secure) |
| Implicit flow | OFF | Deprecated — do not use |
| Service accounts roles | OFF | Enable for machine-to-machine auth |
| OIDC CIBA grant | OFF | Consumer-to-business auth (advanced) |
Click Next.
Configure Client — Login Settings
| Setting | Value | Explanation |
|---|---|---|
| Root URL | http://localhost:3000 | Base URL of the application |
| Home URL | http://localhost:3000 | Landing page after login |
| Valid redirect URIs | http://localhost:3000/* | Critical — only these URIs can receive auth codes |
| Valid post logout redirect URIs | http://localhost:3000/* | Where to redirect after logout |
| Web origins | http://localhost:3000 | Allowed 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/callbacknothttps://app.example.com/*) - Never use
*as a wildcard alone — always prefix with a path - Never use
http://localhostin production (use HTTPS) - Validate redirect URIs during security reviews
Click Save.
Retrieve Client Secret
After saving, the client configuration page opens:
- Go to the Credentials tab
- You will see the Client secret:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - 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
- Go to the Roles tab within the client configuration
- Click Create role
- Role name:
admin - Description:
Web app administrator - Click Save
- Repeat: Create role
editorwith descriptionContent editor
Test the OIDC Flow
Keycloak provides a built-in test endpoint to verify the client configuration:
# 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- Open the URL constructed above in a browser
- Log in as alice / Alice@Lab2026
- 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)
# Step 2: Exchange the auth code for tokens (using curl)# Replace {AUTH_CODE} with the code from the redirect URLAUTH_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" | jqResponse:
{ "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
# 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 | \ jqThe 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
- Go to Clients → Create client
- Client ID:
spa-app - Name:
Single Page App (Lab) - Click Next
- Client authentication: OFF (public client)
- Standard flow: ON
- Direct access grants: OFF
- Click Next
- Valid redirect URIs:
http://localhost:5173/* - Web origins:
http://localhost:5173 - Click Save
Public Client vs Confidential Client
| Aspect | Confidential | Public |
|---|---|---|
| Secret | Yes (shared secret) | No |
| Code flow | Authorization Code + PKCE | Authorization Code + PKCE (required) |
| Security | Secret can be stolen from server | No secret to steal |
| Use case | Server-rendered web apps | SPAs, mobile apps |
| Token storage | Backend session | Browser 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
- Go to Clients → Create client
- Client ID:
saml-app - Name:
SAML Application (Lab) - Click Next
- Client authentication: ON (SAML entities are always confidential)
- Toggle to SAML protocol (click the protocol dropdown and select SAML)
- Capability config appears — configure:
| Setting | Value | Explanation |
|---|---|---|
| SAML protocol | Selected | Switch from OIDC to SAML |
- Click Next
Configure SAML Settings
| Setting | Value | Explanation |
|---|---|---|
| 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:
| Section | Setting | Value | Explanation |
|---|---|---|---|
| SAML Capabilities | Assertion Consumer Service POST Binding URL | http://localhost:8081/saml/callback | Where the SP receives SAML assertions |
| Assertion Consumer Service Redirect Binding URL | (leave empty) | Use POST binding for production | |
| Logout Service POST Binding URL | http://localhost:8081/saml/logout | SLO endpoint | |
| Logout Service Redirect Binding URL | (leave empty) | Redirect binding for logout | |
| SAML General | Encrypt Assertions | ON | Encrypt SAML assertions |
| Client Signature Required | ON | Require signed authn requests | |
| Force POST Binding | ON | Always use POST (more secure) | |
| Include Authn Statement | ON | Include authentication context | |
| Sign Documents | ON | Sign SAML documents | |
| Optimize Lookup | ON | Cache for performance | |
| SAML Assertions | Signature Algorithm | RSA-SHA256 | Modern, secure |
| SAML Signature Key Name | KEY_ID | Use Key ID in signature | |
| Canonicalization Method | http://www.w3.org/2001/10/xml-exc-c14n# | Standard |
SAML vs OIDC — Protocol Decision Guide
| Scenario | Use |
|---|---|
| Modern web/mobile app | OIDC — 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 auth | OIDC — designed for API access with access tokens |
| Mobile app auth | OIDC — native OIDC SDKs, PKCE support |
Export SAML Metadata
SAML service providers need IdP metadata to establish trust:
- In the client page, go to the Action menu (top-right)
- Click Download adapter config
- Select SAML Metadata format
- The metadata XML contains:
- IdP entity ID
- SSO endpoint URL
- Logout endpoint URL
- Signing certificate (public key)
- 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:
cd ~/keycloak-labdocker compose up -dAdd 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:
# Create an LDIF file with test userscat > test-users.ldif << 'EOF'dn: ou=people,dc=lab,dc=corpobjectClass: organizationalUnitou: people
dn: cn=John Doe,ou=people,dc=lab,dc=corpobjectClass: inetOrgPersoncn: John Doesn: Doeuid: jdoemail: jdoe@lab.corpuserPassword: John@Ldap2026
dn: cn=Jane Smith,ou=people,dc=lab,dc=corpobjectClass: inetOrgPersoncn: Jane Smithsn: Smithuid: jsmithmail: jsmith@lab.corpuserPassword: Jane@Ldap2026
dn: ou=groups,dc=lab,dc=corpobjectClass: organizationalUnitou: groups
dn: cn=engineering,ou=groups,dc=lab,dc=corpobjectClass: groupOfNamescn: engineeringmember: cn=John Doe,ou=people,dc=lab,dc=corpmember: cn=Jane Smith,ou=people,dc=lab,dc=corpEOF
# Import into OpenLDAPdocker compose exec openldap ldapadd -x -D "cn=admin,dc=lab,dc=corp" \ -w "Ldap@dmin2026" -f test-users.ldif -H ldap://localhostConfigure User Federation in Keycloak
- In Keycloak Admin Console → sandbox realm
- Go to User Federation in the sidebar
- Click Add provider → ldap
- Configure:
| Setting | Value | Explanation |
|---|---|---|
| Console display name | Lab OpenLDAP | Display name |
| Vendor | Other | OpenLDAP is not Active Directory |
| Connection URL | ldap://openldap:389 | Docker service name (internal network) |
| Bind DN | cn=admin,dc=lab,dc=corp | LDAP admin user |
| Bind Credential | Ldap@dmin2026 | LDAP admin password |
| Authentication Type | simple | Password-based bind |
Click Test connection — you should see a success message
Configure LDAP Searching and Updating:
| Setting | Value | Explanation |
|---|---|---|
| Users DN | ou=people,dc=lab,dc=corp | Where to search for users |
| User Object Classes | inetOrgPerson, organizationalPerson | LDAP object class for users |
| Username LDAP Attribute | uid | Maps to Keycloak username |
| RDN LDAP Attribute | uid | Relative DN |
| UUID LDAP Attribute | entryUUID | Unique identifier |
| User Email Attribute | mail | Email mapping |
| User First Name Attribute | givenName | (not in our LDIF, leave for now) |
| User Last Name Attribute | sn | Last name mapping |
| Read Only | ON | Keycloak reads from LDAP, does not write back |
| Edit Mode | READ_ONLY | Changes happen at the LDAP source |
| Sync Registrations | OFF | Users register in LDAP, not Keycloak |
Click Save
Go to the Mappers tab of the LDAP provider
Click Add mapper → select user-attribute-ldap-mapper
Add a mapper for
givenName→ first name (since our LDIF hascnbut notgivenNameexplicitly):
| Setting | Value |
|---|---|
| Name | givenName |
| LDAP Attribute | cn |
| User Model Attribute | firstName |
| Always Read Value From LDAP | ON |
| Is Mandatory In LDAP | OFF |
Click Save.
Sync Users from LDAP
- Go to the LDAP provider settings
- Click Synchronize all users
- The sync will import
jdoeandjsmithas Keycloak users - Go to Users — you should see them listed
- 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)
- Federation Link:
Test LDAP Authentication
- Open a private/incognito browser window
- Go to: http://localhost:8080/realms/sandbox/account/
- Log in as
jdoewith passwordJohn@Ldap2026 - Authentication succeeds against OpenLDAP
LDAP Sync Strategies
| Strategy | When | Behaviour |
|---|---|---|
| Synchronize all users | Initial setup | Imports all LDAP users matching the filter |
| Synchronize changed users | Periodic sync | Imports only users modified since last sync |
| Periodic sync (on) | Production | Keycloak polls LDAP on a schedule (default: 1 day) |
| Periodic sync (off) | Manual-only | Sync 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.
Navigate to Authentication
- In the sandbox realm, go to Authentication in the sidebar
- Click the Policies tab
Configure MFA Policy
| Setting | Value | Explanation |
|---|---|---|
| OTP Type | totp | Time-based One-Time Password (Google Authenticator, Authy) |
| OTP Algorithm | SHA1 | Standard for TOTP compatibility |
| OTP Digits | 6 | Standard 6-digit codes |
| OTP Token period | 30 | Code changes every 30 seconds |
| Initial Counter | 2 | For HOTP only — ignored for TOTP |
| Number of supported OTPs | 1 | Number of concurrent OTP tokens per user |
Configure Required MFA for All Users
- Go to the Flows tab
- Click Duplicate on the
browserflow (never modify built-in flows) - Name the copy:
browser-with-mfa - Edit the new flow:
| Step | Action |
|---|---|
| Cookie | REQUIRED (existing session check) |
| OTP Form | REQUIRED (always require OTP after password) |
| KDC Identity (Kerberos) | DISABLED (not needed in lab) |
- Click Bind flow → select Browser Flow → choose
browser-with-mfa
Make MFA Required via Authentication Policy
- Go to Authentication → Policies tab
- Click Require OTP under “Required actions for all users”
- Click Register
Test MFA
- Log out of the admin console
- Log back in as
alice - 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] │└─────────────────────────────────────────────────────────┘- Set up your authenticator app and submit the code
- 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 Name | Purpose |
|---|---|
| Browser | Interactive web login (username/password → optional MFA) |
| Direct grant | OAuth2 Resource Owner Password Credentials grant (API-based) |
| Registration | User self-registration flow |
| Reset credentials | Forgot password flow |
| First broker login | First-time login via social/identity provider broker |
| CLI | Command-line (keycloak.css) authentication |
Creating a Custom Flow
To add “Passwordless” authentication (skip password, use WebAuthn only):
- Authentication → Flows → Duplicate
browser→ name itpasswordless - Remove the password form step
- Add WebAuthn Passwordless as the only REQUIRED step
- 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:
# Keycloak OIDC configuration for a Spring Boot applicationKEYCLOAK_AUTH_SERVER_URL=http://localhost:8080KEYCLOAK_REALM=sandboxKEYCLOAK_CLIENT_ID=web-appKEYCLOAK_CLIENT_SECRET=<your-client-secret>KEYCLOAK_SSL_REQUIRED=external
# OIDC discovery URL (auto-configures endpoints)# http://localhost:8080/realms/sandbox/.well-known/openid-configurationThe 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
| Symptom | Cause | Solution |
|---|---|---|
| ”Invalid redirect URI” | Redirect URI does not match client config | Check Valid Redirect URIs in client settings — must match exactly |
| ”Invalid client secret” | Wrong secret or client is public | Verify Credentials tab, ensure Client Authentication is ON |
| ”Invalid token” | Token expired or malformed | Access tokens expire in 300 seconds (default) — use refresh token |
| Users can log in but no roles in token | Roles not mapped to client | Add “Client roles” or “Realm roles” mapper in Client Scopes |
| LDAP sync fails | Connection or credentials | Test connection button. Check docker compose logs openldap |
| LDAP users cannot log in | Wrong password or bind issue | Verify LDAP password. Check “Test authentication” in User Federation |
| SAML ACS error | ACS URL mismatch | Verify SP’s ACS URL matches Keycloak’s client configuration |
| Infinite redirect loop | Session cookie not set | Ensure Keycloak URL is correct, check SameSite cookie settings |
Verification Checklist
| # | Task | Status | Verification |
|---|---|---|---|
| 1 | OIDC confidential client created | ☐ | Client listed in Clients page with Client Authentication ON |
| 2 | Client secret retrieved | ☐ | Credentials tab shows secret |
| 3 | Auth code flow tested | ☐ | Successfully exchanged code for tokens |
| 4 | JWT token decoded with user info | ☐ | Payload shows Alice’s name, email, roles |
| 5 | OIDC public client created | ☐ | Client Authentication OFF, PKCE-capable |
| 6 | SAML client created | ☐ | SAML metadata XML can be exported |
| 7 | OpenLDAP deployed and populated | ☐ | phpLDAPadmin shows users at http://localhost:8090 |
| 8 | User Federation configured | ☐ | LDAP connection test succeeds |
| 9 | LDAP users synced to Keycloak | ☐ | jdoe and jsmith visible in Users |
| 10 | LDAP authentication works | ☐ | jdoe can log into Account Console |
| 11 | TOTP MFA configured and tested | ☐ | Login 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.