Notes on

Keycloak: Identity and Access Management for Modern Applications

by Stian Thorgersen & Pedro Igor Silva

| 27 min read


Chapter 1: Getting Started with Keycloak

Keycloak is an Identity and Access Management (IAM) tool.

OpenID Connect is based on OAuth 2.0.

OTP = One Time Password.

Keycloak has 2 consoles:

  • Admin: configure and manage Keycloak
  • Account: end users can manage their accounts

Realms are basically like tenants. It’s fully isolated from other realms; it has its own configuration, and its own set of applications and users.

Chapter 2: Securing Your First Application

The demo application in the book has a Login button that redirects the user to a Keycloak login page.
When they authenticate with Keycloak, they’re redirected back to the app with a authorization code. This then invokes Keycloak to exchange the authorization code for the tokens:

  • ID token: provides app info about the authenticated user
  • Access token: application includes this token when making requests to a service, allowing it to verify whether the requests should be permitted
  • Refresh token: ID and access token have short expirations, so this is used by the app to obtain new keys

This is the authorization code flow in OpenID Connect.

Keycloak uses JSON Web Signature (JWS) by default as the token format.
They are often referred to as non-opaque tokens, meaning the contents of the token are directly visible to the application.

It also contains a digital signature that makes it possible to verify that the token was issued by Keycloak. So the backend can both verify the token & read its contents without a request to Keycloak.

When you set up clients to use Keycloak, there are some important settings to note:

  • Valid redirect URIs: important in an OIDC authorization code flow when a client-side application is used.
    • A client-side application is unable to have any credentials as they would be visible to end users. To prevent malicious apps from masquerading as the real app, the valid redirect URI tells Keycloak to only redirect users to a URL that matches a valid URI.
  • Valid post redirect URIs: same as before, but for logout requests rather than login requests.
  • Web origins: registers the valid web origins of the app for CORS requests. To get tokens from Keycloak, the frontend app has to send an AJAX request to Keycloak, and browsers won’t permit requests from one web origin to another, unless CORS is used.

You can add unmanaged attributes to users (basically store any key-value pair on their id & access tokens) by first enabling it in your realm settings and then setting it under the user’s attributes.

Chapter 3: Brief Introduction to Standards

  • OAuth 2.0
  • OpenID Connect
  • JSON Web Tokens (JWT)
  • Security Assertion Markup Language 2.0 (SAML 2.0)

Authorizing applications access with OAuth 2.0

OAuth 2.0 is a industry-standard protocol for authorization.

Central to OAuth 2.0 is the OAuth 2.0 framework.

Sharing user data to 3rd party apps is easy, it doesn’t require sharing user credentials, and you control what data is shared.
OAuth 2.0 isn’t just for dealing with 3rd party applications, but also for limiting access to your own applications.

Four roles defined in OAuth 2.0:

  • Resource owner: typically the end user that owns the resource an app wants to access
  • Resource server: the service hosting the protected resources
  • Client: the app that wants to access the resource
  • Authorization server: the server issuing access to the client (what Keycloak does)

In an OAuth 2.0 protocol flow, the client requests access to a resource on behalf of a resource owner from the authorization server.
The authorization server issues limited access to the resource in the form of an access token.
After receiving the access token, the client can access the resource at the resource server by including the access token in the request.

There are various flows you could use, depending on your app type & use case:

  • If app accesses resource on behalf of itself (app is the resource owner): use Client Credentials Flow
  • If app runs on a device without a browser/is input constrained (e.g. smart TV): use Device flow.
  • Otherwise, use the authorization code flow.

Deprecated: Implicit flow, Resource Owner Password Credentials flow.

Authorization code flow in OAuth 2.0:

  1. App prepares authorization request, tells browser to redirect to Keycloak
  2. User’s browser is sent to Keycloak at the authorization endpoint
  3. If user isn’t authenticated, Keycloak authenticates the user. Once authenticated, Keycloak asks the user for consent to allow the app to access the service for the user
  4. App gets authorization code from Keycloak in the form of authorization response
  5. App exchanges authorization code for an access token through an access token request to the token endpoint at Keycloak
  6. App can now use the access token to invoke protected resources

There are two client types in an OAuth 2.0 flow:

  • Confidential: e.g. server-side apps that are able to safely store credentials, which can be used to authenticate with the authorization server
  • Public: e.g. client-side apps that can’t safely store credentials. Since they can’t authenticate with the authorization server, there are two safeguards:
    • Authorization server only sends authorization code to app hosted on a preconfigured URL
    • Proof Key for Code Exchange (PKCE, RFC 7636) prevents anyone that intercepts an authorization code from exchanging it for an access token

Access tokens are passed from the app to services. Usually have short lifetime.
Apps can get new access tokens without going through the complete flow with a refresh token.

Bearer Tokens (RFC 6750): OAuth 2.0 doesn’t describe the type of access token nor how it should be used. These are the most commonly used type of access tokens & are usually sent to resource servers through the HTTP Authorization header. Can also be sent in form-encoded body or as query parameter (but avoid the latter: security issue!).

Token Introspection (RFC 7662): In OAuth 2.0, the contents of access tokens are opaque to applications, so the content of the access token isn’t readable by the application. The token introspection endpoint lets the client get info about the access token without understanding its format.

Token Revocation (RFC 7009): OAuth doesn’t consider how tokens are revoked. This is done by the token revocation endpoint.

So with OAuth 2.0, you can grant access to resources. What about authentication of users? That’s done by an extension to OAuth 2.0: OpenID Connect.
That is, OAuth 2.0 is a protocol for authorization, not authentication. OpenID Connect builds on top of OAuth 2.0 to add an authentication layer.

Authenticating users with OpenID Connect

  • OAuth 2.0 is a protocol for authorization but does not handle authentication.
  • OpenID Connect (OIDC) builds on OAuth 2.0 to add an authentication layer, making it easier for websites to manage user authentication.
  • OIDC reduces the need for multiple user authentications and passwords across different websites, enabling social logins (e.g., using Google) and centralized authentication within enterprises.
  • Key Roles in OIDC:
    • End User: The person that is authenticating (similar to the resource owner in OAuth 2.0).
    • Relying Party (RP): The application that requests user authentication (relies on the OpenID Provider).
    • OpenID Provider (OP): The identity provider that authenticates the user (e.g., Keycloak).
  • In an OIDC protocol flow: RP requests the identity of the end user from the OP.
  • Uses the Authorization Code grant type from OAuth 2.0, but client includes scope=openid in the initial request, making it an authentication request rather than authorization request.
  • OIDC Flows:
    • Authorization Code Flow: Similar to OAuth 2.0’s Authorization Code grant type; returns an authorization code which can be exchanged for an ID token, access token, and refresh token.
    • Hybrid Flow: Returns an ID token with the authorization code.
    • Implicit Flow: Considered legacy and not recommended.
  • OIDC does not have equivalents for OAuth 2.0’s Client Credential flow and Device flow since they do not involve user authentication.
  • OIDC Enhancements:
    • ID Token: Uses JWT format, allowing clients to discover user information in a standard way (it isn’t opaque; has a well-specified format). And the values, called claims, within the token can be directly read by the client.
  • Userinfo Endpoint: Returns standard claims about the user when invoked with an access token.
  • Additional Specifications:
    • Discovery: Dynamic discovery of OP information.
    • Dynamic Registration: Clients can dynamically register with the OP.
    • Session Management: Monitors user authentication sessions and manages logout.
    • Front-Channel Logout: Single sign-out using embedded iframes.
    • Back-Channel Logout: Single sign-out using back-channel requests.
  • Financial-Grade API (FAPI): Provides best practices for using OIDC in high-risk scenarios, though not specific to finance. There’s a set of profiles from this working group if you want additional security.
  • Libraries typically handle the complexity of OIDC for developers, making it easier to implement correctly in applications.

Leveraging JWT for tokens

Keycloak uses JWT for access tokens.
Pros:

  • Standard format
  • Based on JSON: any programming language can read that
  • Resource servers can directly read the values of the access token; don’t always have to make requests to the OAuth 2.0 token introspection endpoint or OIDC UserInfo endpoint.

For security:

  • Don’t accept alg=none
  • Only use a key for the algorithm and the use it’s intended for. Don’t blindly trust the values in the JWT header.

Pick up a trusted JWT library & use it correctly. Or even better, an OIDC/OAuth 2.0 library that supports JWT as the access token.
If you can’t, then maybe use the token introspection endpoint rather than try to validate the token yourself.

Understanding why SAML 2.0 is still relevant

  • SAML 2.0:
    • Mature, robust protocol for authentication/authorization
    • Widely used for enterprise single sign-on
    • Ratified as OASIS Standard in 2005 (so been around for a long time!)
    • Available in many enterprise and SaaS applications
  • Easily allow existing users to authenticate to new apps you want to deploy
  • Benefits of SAML 2.0:
    • Easy integration with existing systems
    • Enables quick access for employees to new applications
  • OpenID Connect vs SAML 2.0:
    • OpenID Connect better for modern architectures (SPAs, mobile, REST APIs, microservices)
    • Simpler for developers (uses JSON, simple query parameters)
    • Recommended to learn OAuth 2.0 and OpenID Connect first
  • SAML 2.0 still relevant because:
    • Often available when OpenID Connect is not
    • May be better for specific use cases
    • May be required by internal policies or compliance
  • Keycloak supports both SAML 2.0 and OpenID Connect
    • Allows seamless combination in single sign-on experience

Chapter 4: Authenticating Users with OpenID Connect

Understanding the Discovery endpoint

  • OIDC Discovery specification improves interoperability and usability for Relying Party libraries
  • Discovery endpoint: <base URL>/.well-known/openid-configuration
    • Lets Relying Party discover useful information about the provider
    • Provides OpenID Provider Metadata
  • Example issuer URL: http://localhost:8080/realms/myrealm
  • Parts of issuer URL:
    • Root URL (e.g., http://localhost:8080)
    • Realm path (e.g., /realms/myrealm)
  • Key metadata fields:
    • authorization_endpoint: URL to use for authentication requests
    • token_endpoint: URL to use for token requests
    • introspection_endpoint: URL for introspection requests
    • userinfo_endpoint: URL for UserInfo requests
    • grant_types_supported: list of supported grant types
    • response_types_supported: list of supported response types
  • Metadata helps Relying Party make decisions about using the OpenID Provider
  • Keycloak supports authorization_code grant type and code and token response types

Authenticating a user

  • OpenID Connect authorization code flow is common for Keycloak authentication
  • Flow: App redirects to Keycloak login → User authenticates → App receives ID token
  • Steps:
    1. User clicks login in app
    2. App generates auth request
    3. 302 redirect to Keycloak auth endpoint
    4. User-agent opens auth endpoint
    5. User enters credentials on Keycloak login page
    6. Keycloak verifies and returns authorization code
    7. App exchanges code for ID token and refresh token
  • OIDC playground app used to demonstrate flow
  • Key parameters for auth request:
    • client_id
    • scope (default: openid)
    • prompt (e.g., none, login)
    • max_age
    • login_hint
  • Authentication request sets response_type to “code”
  • After user login, app receives authorization code
  • Code can be exchanged for ID token and refresh token

Example Authentication Request URL:

http://localhost:8040/realms/christian/protocol/openid-connect/auth?client_id=oidc-playground&response_type=code&redirect_uri=http://localhost:8000/&scope=openid

So the query parameters are:

  • client_id=oidc-playground, which is the client ID for the app registered with Keycloak.
  • response_type=code means the application wants to receive an authorization code from Keycloak.
  • redirect_uri=http://localhost:8000/
  • scope=openid means we’re doing an OpenID request.

Response was 302 Found. And you get authentication response like seen below, which is the authorization code—the code the app uses to get the ID token and refresh token.

code=f59656d4-4404-4886-aead-188c3474a5d5.70088f6b-6dc7-441e-82fb-bfcdc0050892.51434aa5-76ed-476a-abab-25d6b5a75420

Now when you exchange it via a Token Request, you’ll send something like:

http://localhost:8040/realms/christian/protocol/openid-connect/token  
  
Query parameters:
	grant_type=authorization_code  
	code=f59656d4-4404-4886-aead-188c3474a5d5.70088f6b-6dc7-441e-82fb-bfcdc0050892.51434aa5-76ed-476a-abab-25d6b5a75420  
	client_id=oidc-playground  
	redirect_uri=http://localhost:8000/

And you get a token response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjUzODkzMzUsImlhdCI6MTcyNTM4OTAzNSwiYXV0aF90aW1lIjoxNzI1Mzg4NTQ2LCJqdGkiOiI0MTkxOTA5OC04OGEyLTQ4ZjAtOGM3ZC1mZjVhYzJjOWI5M2MiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmNDU5MGFiOS0zMDRiLTQ0M2ItYmUxMy1mM2JlMGI5NDVkOTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiI3MDA4OGY2Yi02ZGM3LTQ0MWUtODJmYi1iZmNkYzAwNTA4OTIiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1jaHJpc3RpYW4iLCJvZmZsaW5lX2FjY2VzcyIsImRuYS1zZGEiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIm15cm9sZSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IkNocmlzdGlhbiBIb3VtYW5uIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hyaXN0aWFuIiwiZ2l2ZW5fbmFtZSI6IkNocmlzdGlhbiIsImZhbWlseV9uYW1lIjoiSG91bWFubiIsImVtYWlsIjoiY2hyaXN0aWFuQGJhZ2VyYmFjaC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzI5MTA4NjI4P3Y9NCJ9.njH4pN_iDhuO9PFjs3RvJJsLGLpXAQdK7ttqw4H-BN-F3_p_x951gr97ppO7-xOUpASQApLo8-nM_hus6szyopB7-AJg4f7DrsbV899wOHiawDmzLEj4hD5Pel8DFt4qiIGDXE-DLMASTDBcA4VfwonEAz9L-K1cLNxo1kCoh9Ed-WRMuhTfz6w6hWOoa4dy_ZjYZSEJrBzKG1uH2phtCltsMXIYUWzqXl1AvBp6oLx7InEUi-3UlME5U931O7nWICyNbGoP4oc0WFqehoUjMjfI2xwpED9qVM2vdvoL-EbuVtp0cxbrZQGWjiY2whe17b62hotmuzOXn9c__b_FOg",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5YzQ3MGEyNS1mMTllLTRiMTUtODU4ZS1mMzk1NmI2ZGQyZTIifQ.eyJleHAiOjE3MjUzOTA4MzUsImlhdCI6MTcyNTM4OTAzNSwianRpIjoiY2FlMzhmMDgtMTNiYS00MDhjLTg2MDctZDU1ZmVhZThkNzI0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDQwL3JlYWxtcy9jaHJpc3RpYW4iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsInN1YiI6ImY0NTkwYWI5LTMwNGItNDQzYi1iZTEzLWYzYmUwYjk0NWQ5MCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiI3MDA4OGY2Yi02ZGM3LTQ0MWUtODJmYi1iZmNkYzAwNTA4OTIiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIHdlYi1vcmlnaW5zIGJhc2ljIGVtYWlsIGFjciByb2xlcyJ9.TOM2bWycKP77Fml553hm24N3iAeogIGFoArcfXkYHf6qqUwa5rYYfqqHPACXUtYOGjnrhKSvpUxIM273Olc3Iw",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjUzODkzMzUsImlhdCI6MTcyNTM4OTAzNSwiYXV0aF90aW1lIjoxNzI1Mzg4NTQ2LCJqdGkiOiIwZTlkMGY4Yy0zM2JiLTRhMDAtYWNmOC1lNjZkYWE3YmQwNWYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6Im9pZGMtcGxheWdyb3VuZCIsInN1YiI6ImY0NTkwYWI5LTMwNGItNDQzYi1iZTEzLWYzYmUwYjk0NWQ5MCIsInR5cCI6IklEIiwiYXpwIjoib2lkYy1wbGF5Z3JvdW5kIiwic2lkIjoiNzAwODhmNmItNmRjNy00NDFlLTgyZmItYmZjZGMwMDUwODkyIiwiYXRfaGFzaCI6IkRKX0xiOE5GalRpVVBUTUJyVUg2Z2ciLCJhY3IiOiIwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJDaHJpc3RpYW4gSG91bWFubiIsInByZWZlcnJlZF91c2VybmFtZSI6ImNocmlzdGlhbiIsImdpdmVuX25hbWUiOiJDaHJpc3RpYW4iLCJmYW1pbHlfbmFtZSI6IkhvdW1hbm4iLCJlbWFpbCI6ImNocmlzdGlhbkBiYWdlcmJhY2guY29tIiwicGljdHVyZSI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8yOTEwODYyOD92PTQifQ.xsXfs0vJQtv93lrNGSeclt6rS22eO6lOg65QeKv6f758NLU8E3-dXlt0Fq_xJMUFhbUvcjMsJ3kF3SJFszDVXLzaNC6hqp6-pKp2j_6cl00FUZqYUNOHXoY5VZKcW0iYeY_oIoZlzgHy3zSq-9vF-MIT4D-anvXEFlILXPXqwdEO1PKyh2EF-bBMX4lmAaY1Fo7mJeN3aKEiJCfjal76AvDGPw1oCkPhVOlm4PIwrNqZFkht_f0yFI8eTxn2QT9Oy516DtgeGYNLOo3hm317yPpCB9ZKKErPPx-LwI0tlD2RcpOY52Iz_vk4-4RihcaCKkNf3fWK0b3uOkQxlB8L7A",
  "not-before-policy": 0,
  "session_state": "70088f6b-6dc7-441e-82fb-bfcdc0050892",
  "scope": "openid profile email"
}

The access_token is the access token from Keycloak, which is a signed JWT.
The token_type is always bearer in Keycloak.
session_state is the ID of the session the user has with Keycloak.
scope: the application requests a scope from Keycloak in the authentication request, but the actual returned scope may not match the requested scope.

Understanding the ID token

  • ID token structure: <Header>.<Payload>.<Signature>
  • Header and payload are Base64 URL-encoded JSON documents
  • Key claims in ID token:
    • exp: Expiration time
    • iat: Issued at time
    • auth_time: Last authentication time
    • jti: Unique token identifier
    • aud: Audience (Relying Party)
    • azp: Authorized party (the party the token was issued to)
    • sub: Unique user identifier
      • Use this when referring to a user instead of email/username, as they may change over time
  • ID tokens have short duration for security
  • Refresh tokens:
    • Longer expiration
    • Used to obtain updated ID tokens
    • Only usable directly with Keycloak
  • Refresh process:
    • Uses grant type refresh_token
    • Includes refresh token and client ID
    • Returns new ID token, access token, and refresh token
  • Important to use updated refresh token for subsequent refreshes
  • Reasons for updating refresh tokens:
    • Key rotation
    • Session idle feature
    • Refresh token leak detection
  • Refreshing allows updating user info without re-authentication

ID Token Header

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "8VSrqK6kvq5ch_39Ndt4WmCWgC6PZB5WOyHxmL7GizY"
}

ID Token Payload

{
  "exp": 1725389335,
  "iat": 1725389035,
  "auth_time": 1725388546,
  "jti": "0e9d0f8c-33bb-4a00-acf8-e66daa7bd05f",
  "iss": "http://localhost:8040/realms/christian",
  "aud": "oidc-playground",
  "sub": "f4590ab9-304b-443b-be13-f3be0b945d90",
  "typ": "ID",
  "azp": "oidc-playground",
  "sid": "70088f6b-6dc7-441e-82fb-bfcdc0050892",
  "at_hash": "DJ_Lb8NFjTiUPTMBrUH6gg",
  "acr": "0",
  "email_verified": true,
  "name": "Christian Houmann",
  "preferred_username": "christian",
  "given_name": "Christian",
  "family_name": "Houmann",
  "email": "[email protected]",
  "picture": "https://avatars.githubusercontent.com/u/29108628?v=4"
}

ID Token Signature

xsXfs0vJQtv93lrNGSeclt6rS22eO6lOg65QeKv6f758NLU8E3-dXlt0Fq_xJMUFhbUvcjMsJ3kF3SJFszDVXLzaNC6hqp6-pKp2j_6cl00FUZqYUNOHXoY5VZKcW0iYeY_oIoZlzgHy3zSq-9vF-MIT4D-anvXEFlILXPXqwdEO1PKyh2EF-bBMX4lmAaY1Fo7mJeN3aKEiJCfjal76AvDGPw1oCkPhVOlm4PIwrNqZFkht_f0yFI8eTxn2QT9Oy516DtgeGYNLOo3hm317yPpCB9ZKKErPPx-LwI0tlD2RcpOY52Iz_vk4-4RihcaCKkNf3fWK0b3uOkQxlB8L7A

Sending a refresh request
Notice the grant_type=refresh_token & that it includes the refresh token & client ID.

http://localhost:8040/realms/christian/protocol/openid-connect/token  

Query parameters:
	grant_type=refresh_token  
	refresh_token=eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5YzQ3MGEyNS1mMTllLTRiMTUtODU4ZS1mMzk1NmI2ZGQyZTIifQ.eyJleHAiOjE3MjUzOTA4MzUsImlhdCI6MTcyNTM4OTAzNSwianRpIjoiY2FlMzhmMDgtMTNiYS00MDhjLTg2MDctZDU1ZmVhZThkNzI0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDQwL3JlYWxtcy9jaHJpc3RpYW4iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsInN1YiI6ImY0NTkwYWI5LTMwNGItNDQzYi1iZTEzLWYzYmUwYjk0NWQ5MCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiI3MDA4OGY2Yi02ZGM3LTQ0MWUtODJmYi1iZmNkYzAwNTA4OTIiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIHdlYi1vcmlnaW5zIGJhc2ljIGVtYWlsIGFjciByb2xlcyJ9.TOM2bWycKP77Fml553hm24N3iAeogIGFoArcfXkYHf6qqUwa5rYYfqqHPACXUtYOGjnrhKSvpUxIM273Olc3Iw  
	client_id=oidc-playground  
	scope=openid

Refresh response (similar to response for original token request)

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjUzODk5MDgsImlhdCI6MTcyNTM4OTYwOCwiYXV0aF90aW1lIjoxNzI1Mzg4NTQ2LCJqdGkiOiJjMTY5OTZkNy1jMGNiLTQyNzUtOTg2ZS1iMDg0MGEwZjc2NTIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmNDU5MGFiOS0zMDRiLTQ0M2ItYmUxMy1mM2JlMGI5NDVkOTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiI3MDA4OGY2Yi02ZGM3LTQ0MWUtODJmYi1iZmNkYzAwNTA4OTIiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1jaHJpc3RpYW4iLCJvZmZsaW5lX2FjY2VzcyIsImRuYS1zZGEiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIm15cm9sZSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IkNocmlzdGlhbiBIb3VtYW5uIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hyaXN0aWFuIiwiZ2l2ZW5fbmFtZSI6IkNocmlzdGlhbiIsImZhbWlseV9uYW1lIjoiSG91bWFubiIsImVtYWlsIjoiY2hyaXN0aWFuQGJhZ2VyYmFjaC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzI5MTA4NjI4P3Y9NCJ9.CdnKm05ALl3nXZTJ2slorNjmsrkLPdJ25R1yDgk2axJJ0zSFgPxetVhqGdDiGCD_bBFIim44-Y1yWOEkqDIxt73bRuQLhiYS2qSnfD2AUMzSfm6ZDalzQLnxpfUMrULkLY9SqE5fiYHgABvXz07LT6ND_Nhl8XnINkc7ULQL6B6K0iSytHEgG3PqYBmM9aIU-KaiCN3TixmEtM3ruGMZWcoK6drt68mgjPnqoWKS719A-y0049U2A8rGMGAMwekcBJ1TvQhfqjM5iRc-oLmBq6tia_-Drit3o4AOsRsTS_QfC4Xv8gLo6YpVyl48GRyiGgW_d2Hh1s7KOfsYFtO_XA",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5YzQ3MGEyNS1mMTllLTRiMTUtODU4ZS1mMzk1NmI2ZGQyZTIifQ.eyJleHAiOjE3MjUzOTE0MDgsImlhdCI6MTcyNTM4OTYwOCwianRpIjoiZjZlYTY1OWUtY2MxOS00NDZlLTgwMmItOGI1MGM1NDc0NzMyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDQwL3JlYWxtcy9jaHJpc3RpYW4iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsInN1YiI6ImY0NTkwYWI5LTMwNGItNDQzYi1iZTEzLWYzYmUwYjk0NWQ5MCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiI3MDA4OGY2Yi02ZGM3LTQ0MWUtODJmYi1iZmNkYzAwNTA4OTIiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIHdlYi1vcmlnaW5zIGJhc2ljIGVtYWlsIGFjciByb2xlcyJ9.MlTWXBx3VkC3paMR8DD6rTUauKyIksZjlKb_S1E46TBBndU0GiGK03k7Tk5aKHfnyrnBRyeQGJkO9ce2JMpQ8g",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjUzODk5MDgsImlhdCI6MTcyNTM4OTYwOCwiYXV0aF90aW1lIjoxNzI1Mzg4NTQ2LCJqdGkiOiJlYmExM2UzZS0zYzg0LTQ3YjUtYjQ2ZS1hZDQ0M2E1M2U4YzciLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6Im9pZGMtcGxheWdyb3VuZCIsInN1YiI6ImY0NTkwYWI5LTMwNGItNDQzYi1iZTEzLWYzYmUwYjk0NWQ5MCIsInR5cCI6IklEIiwiYXpwIjoib2lkYy1wbGF5Z3JvdW5kIiwic2lkIjoiNzAwODhmNmItNmRjNy00NDFlLTgyZmItYmZjZGMwMDUwODkyIiwiYXRfaGFzaCI6IjFTZ3R5dDJ6T194RERDa29EalFNamciLCJhY3IiOiIwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJDaHJpc3RpYW4gSG91bWFubiIsInByZWZlcnJlZF91c2VybmFtZSI6ImNocmlzdGlhbiIsImdpdmVuX25hbWUiOiJDaHJpc3RpYW4iLCJmYW1pbHlfbmFtZSI6IkhvdW1hbm4iLCJlbWFpbCI6ImNocmlzdGlhbkBiYWdlcmJhY2guY29tIiwicGljdHVyZSI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8yOTEwODYyOD92PTQifQ.J2IRHn8FAdoHGf-Djt1thY3MHeXiFyC6La74vs2zRUUhLNzb66H5gwNVJAief-25ktK75zkOFsNt4fnUIBSlxd4KJYp_Ijz9ujh5Uj2E1ybqKwSBh_yYfnV6vAbNAWhccumYR5L3IL1ldefrCVtt6poLHCGWhAE3Z2b3JnZ1QVxXSMqyKeJnpKk4tERV6SqqG96S5QZrjQ1ag6Yi9VE7NPBs6YTiwh587vBDN0IAfkM5RZY_zTK49l5feMOVR_JJHLptfV2XR9nabUBULchqquqXnxadgLX9zorcLqSgUl3kX5ejDOGk-UuICTebKs4iX60PQpjf_DZPw36txZtktA",
  "not-before-policy": 0,
  "session_state": "70088f6b-6dc7-441e-82fb-bfcdc0050892",
  "scope": "openid profile email"
}

Adding a custom property

  1. Add custom property to user:
    • In Keycloak Admin Console, go to Attributes
    • Set key (myattribute) and value (myvalue)
  2. Create client scope:
    • Go to Client Scopes, create new scope (myscope)
  3. Add custom attribute to client scope:
    • Create mapper (User Attribute)
    • Set name (myattribute), user attribute (myattribute), token claim name (myattribute), and claim JSON type (String)
    • Enable “Add to ID Token”
  4. Add client scope to client:
    • Go to Clients, select oidc-playground
    • Add myscope as Optional client scope
  5. Test in playground application:
    • Send refresh request (custom attribute not added)
    • Send new authentication request with “openid myscope” scope
      • We have to explicitly request it because the scope is added as an optional client scope for the client. If it was added to the default client scopes, it would always be added for the client.
    • Generate and send authentication request
    • Send token request
    • Custom claim is now in ID token payload
  6. Next step: Add roles to ID token

A client scope allows creating multiple re-usable groups of claims that are added to tokens issued to a client.

Adding roles to the ID token

  • Roles not added to ID token by default
  • To add roles:
    • Go to Client Scopes > roles > Mappers > realm roles
    • Enable “Add to ID Token” and save
  • Ensure user has associated realm role
  • Refresh token in playground app to see realm_access in ID token
  • By default, all roles added to all clients (not ideal for security: want to limit what access each individual client has)
  • ID token: authenticates user to specific client
  • Access token: used to access other services

Invoking the UserInfo endpoint

  • UserInfo endpoint can be invoked with an access token from OIDC flow
  • UserInfo response contains simpler user attributes compared to ID token
  • UserInfo endpoint information can be configured like ID token
  • Custom information can be added to UserInfo via client protocol mappers
    • Clients → select client → select client scope → click Configure new mapper → choose claim type (e.g. hardcoded) → Ensure Add to userinfo is on.
  • UserInfo endpoint only works with access tokens from OIDC flow
  • Removing “openid” scope from authentication request results in:
    • No ID token in response
    • UserInfo request fails

UserInfo Request

http://localhost:8040/realms/christian/protocol/openid-connect/userinfo  
  
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjU0NzI0MzUsImlhdCI6MTcyNTQ3MjEzNSwiYXV0aF90aW1lIjoxNzI1NDcyMTMzLCJqdGkiOiIyMTQ3ZDQ3YS1jYWZiLTQ0MDYtODY2Ny1hODlmZGM4NWRhN2IiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmNDU5MGFiOS0zMDRiLTQ0M2ItYmUxMy1mM2JlMGI5NDVkOTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvaWRjLXBsYXlncm91bmQiLCJzaWQiOiJhOTVhNTk0NS0xNzE2LTRhMjAtOWNiYy0zZTg3NDBkZTkyZGQiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1jaHJpc3RpYW4iLCJvZmZsaW5lX2FjY2VzcyIsImRuYS1zZGEiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIm15cm9sZSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IkNocmlzdGlhbiBCLiBCLiBIb3VtYW5uIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hyaXN0aWFuIiwiZ2l2ZW5fbmFtZSI6IkNocmlzdGlhbiBCLiBCLiIsImZhbWlseV9uYW1lIjoiSG91bWFubiIsIm15YXR0cmlidXRlIjoibXl2YWx1ZSIsImVtYWlsIjoiY2hyaXN0aWFuQGJhZ2VyYmFjaC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzI5MTA4NjI4P3Y9NCJ9.f01hBsyQ1HG53poXQLBExZs1yhfUjGMDhKElvJ6A5BZygTztVZjxPKoKMNJ4OJkZwBouNabUXr1wqZEzk17SjptDe8Ku9fR0ZynmaLJO1MaFyrz70DFxjhyKA9Pt0KXuNyv2dL4T7RcVQeLXx0lQNfQbwh3CP7C2SrHnXCcv4BHQ5iJP67nRuY-R86yCCqT4aVCoe6FYfzgFMon9OoEYOG_FfqJpA7cEVmn6SqrRohPl4DEaKY1iU4QudxYQM0yWbg7CqUhAnAxW-elirkZqP1YTowBtC0ngsqmHLlGkLCbjRx-dEL3g0ugAK51DszezYzgqTBRQXU7KaaTrQTGRWg

UserInfo Response

{
  "sub": "f4590ab9-304b-443b-be13-f3be0b945d90",
  "email_verified": true,
  "name": "Christian B. B. Houmann",
  "preferred_username": "christian",
  "given_name": "Christian B. B.",
  "family_name": "Houmann",
  "email": "[email protected]",
  "picture": "https://avatars.githubusercontent.com/u/29108628?v=4"
}

Dealing with users logging out

  • Logout in SSO can be complex, especially for instant logout across all apps
  • Logout initiation:
    • User clicks e.g. logout button
    • App sends request to OpenID Connect RP-Initiated logout
    • Redirect to Keycloak End Session endpoint
  • When Keycloak receives the logout request, it will:
    • Notify other clients in session
    • Invalidate session (makes all tokens invalid)
  • Logout strategies:
    1. Leverage token expiration:
      • Simple but may take a few minutes to fully logout
      • Good for public clients
    2. OIDC Session Management:
      • Uses hidden iframe to monitor session cookie
      • Becoming less relevant due to browser restrictions
    3. OIDC Back-Channel Logout:
      • Apps register endpoint to receive logout events
      • Keycloak sends logout token (signed JWT) to registered apps
      • Effective for server-side apps, complex for clustered apps
  • OIDC Front-Channel Logout:
    • Uses hidden iframes, but unreliable and affected by browser restrictions
  • Recommended approach:
    • Use short application sessions and token expiration
    • For instant logout, use OIDC Back-Channel Logout

Chapter 5: Authorizing Access with OAuth 2.0

Obtaining an access token

  • OAuth 2.0 Authorization Code grant type is common for obtaining access tokens
  • Process:
    1. App redirects user to Keycloak
    2. Keycloak authenticates user
    3. User may grant consent
    4. Keycloak returns access token to app
    5. App uses token to access API
  • Key access token fields:
    • aud: intended recipients
    • realm_access: global roles
    • resource_access: client roles
    • scope: included scope

Header

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "8VSrqK6kvq5ch_39Ndt4WmCWgC6PZB5WOyHxmL7GizY"
}

Payload

{
  "exp": 1725475315,
  "iat": 1725475015,
  "auth_time": 1725475015,
  "jti": "b0ac64d7-f11e-42ea-aabe-f207f7f6d759",
  "iss": "http://localhost:8040/realms/christian",
  "aud": "account",
  "sub": "f4590ab9-304b-443b-be13-f3be0b945d90",
  "typ": "Bearer",
  "azp": "oauth-playground",
  "sid": "4ba5cd1d-dfc7-46c3-ba7c-0ad5ef1817be",
  "acr": "1",
  "allowed-origins": [
    "http://localhost:8000"
  ],
  "realm_access": {
    "roles": [
      "default-roles-christian",
      "offline_access",
      "dna-sda",
      "uma_authorization",
      "myrole"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "profile email",
  "email_verified": true,
  "name": "Christian B. B. Houmann",
  "preferred_username": "christian",
  "given_name": "Christian B. B.",
  "family_name": "Houmann",
  "email": "[email protected]",
  "picture": "https://avatars.githubusercontent.com/u/29108628?v=4"
}

Signature

e3mf_BP3x7NoOqgTojWHGvOBTsnU1NpumER63U7XbbWAwqBa_I4CQU3fqPaKo0a6wE-XLksICcCpDnCgRtG7CE7ySjopAFd934BasD6YOjdbcppauJ56AiHyV9cMawfJ14iS--v3OgYBgBH60lWxW_4JecQHo7ohV3mOyWDaitp66Nh7goxydWIOVBGyjeKQxthwCgQHN6nCPlhh74NfmSHu5LTnkN19yxRndP0fikx0PJMv6XD839h31XViRBu0IYJDUTVYEut6MlTM-g7eaQGJyKdxQF_MA02Zk3Do18z4enJzMPvDrR9Ktq5PoT2uKOYz9ycTxPvFzSS5EN4XKw

Encoded

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4VlNycUs2a3ZxNWNoXzM5TmR0NFdtQ1dnQzZQWkI1V095SHhtTDdHaXpZIn0.eyJleHAiOjE3MjU0NzUzMTUsImlhdCI6MTcyNTQ3NTAxNSwiYXV0aF90aW1lIjoxNzI1NDc1MDE1LCJqdGkiOiJiMGFjNjRkNy1mMTFlLTQyZWEtYWFiZS1mMjA3ZjdmNmQ3NTkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwNDAvcmVhbG1zL2NocmlzdGlhbiIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmNDU5MGFiOS0zMDRiLTQ0M2ItYmUxMy1mM2JlMGI5NDVkOTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvYXV0aC1wbGF5Z3JvdW5kIiwic2lkIjoiNGJhNWNkMWQtZGZjNy00NmMzLWJhN2MtMGFkNWVmMTgxN2JlIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtY2hyaXN0aWFuIiwib2ZmbGluZV9hY2Nlc3MiLCJkbmEtc2RhIiwidW1hX2F1dGhvcml6YXRpb24iLCJteXJvbGUiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IkNocmlzdGlhbiBCLiBCLiBIb3VtYW5uIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hyaXN0aWFuIiwiZ2l2ZW5fbmFtZSI6IkNocmlzdGlhbiBCLiBCLiIsImZhbWlseV9uYW1lIjoiSG91bWFubiIsImVtYWlsIjoiY2hyaXN0aWFuQGJhZ2VyYmFjaC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzI5MTA4NjI4P3Y9NCJ9.e3mf_BP3x7NoOqgTojWHGvOBTsnU1NpumER63U7XbbWAwqBa_I4CQU3fqPaKo0a6wE-XLksICcCpDnCgRtG7CE7ySjopAFd934BasD6YOjdbcppauJ56AiHyV9cMawfJ14iS--v3OgYBgBH60lWxW_4JecQHo7ohV3mOyWDaitp66Nh7goxydWIOVBGyjeKQxthwCgQHN6nCPlhh74NfmSHu5LTnkN19yxRndP0fikx0PJMv6XD839h31XViRBu0IYJDUTVYEut6MlTM-g7eaQGJyKdxQF_MA02Zk3Do18z4enJzMPvDrR9Ktq5PoT2uKOYz9ycTxPvFzSS5EN4XKw
  • User consent is crucial for applications accessing third-party services on behalf of users
  • Keycloak allows configuring applications to require or not require consent
  • External applications should always require consent
  • Trusted applications may not require consent, with admin granting access on users’ behalf
  • Consent can be enabled in Keycloak admin console under client’s Login settings
  • OAuth 2.0 uses scopes to define limited access to user accounts
  • Applications can request additional scopes as needed, reducing initial user intimidation

Adding a new client scope with consent

  1. Admin console → Client scopes → Create Client Scope
    • Name: albums
    • Display on consent screen
    • Consent screen text: View your photo albums
    • Include in token scope
  2. Go to client → Client scopes → Add client scope → albums → Add as e.g. Optional
  3. Use albums in the scope when making authorization requests

Keycloak remembers what consent a user has given a particular application, so the user won’t be prompted again.

Limiting the access granted to access tokens

  • Limiting access granted to access tokens is crucial for security
  • Users can revoke application access through the account console
  • Strategies to limit access for specific tokens:
    • Audience: Lists resource providers that should accept the token
    • Roles: Controls what roles an application can access on behalf of the user
    • Scope: Created through client scopes, limits application access to specific scopes
  • Audience:
    • Can be added manually with a protocol mapper or automatically through client roles
    • Backend can be configured to check the audience in the token
  • Roles:
    • Used to grant user permissions and limit application permissions
    • Roles in tokens are the intersection of user roles and client-allowed roles
    • Full Scope Allowed option includes all user roles in tokens by default
  • Scope:
    • Default OAuth 2.0 mechanism to limit token permissions
    • In Keycloak, scopes are mapped to client scopes
    • Useful for third-party applications requiring user consent
    • Should be limited in number and easily understandable by users
    • Can be unique within an organization, often prefixed with service name or URL
    • Example scopes: albums:view, albums:create, albums:delete
  • Implementing scopes:
    • Create client scopes in Keycloak admin console
    • Add client scopes to clients as default or optional
    • Use incremental authorization for better user experience
  • Consent:
    • Can be required for applications to access user resources
    • Users grant permission for specific scopes

Validating Access Tokens

  • Two methods to validate access tokens: invoking the token introspection endpoint or directly verifying the token
  • Token introspection endpoint:
    • Simpler approach, less tied to Keycloak
    • OAuth 2.0 standard for querying token state and claims
    • Allows non-self-contained tokens
    • Downsides: extra latency and server load
    • Can use caching to reduce re-validation frequency
  • Direct verification of access tokens:
    • Applicable for Keycloak’s JWT format
    • Allows parsing and reading token contents directly
    • Requires understanding of JWT and token information
    • Does not validate user session (e.g., user sign-out)
  • Steps for direct token verification:
    • Retrieve public signing keys from JWKS endpoint
    • Verify token signature
    • Check token expiration
    • Verify issuer, audience, and token type
    • Validate any other relevant claims
  • Keycloak client libraries (application adapters) use direct verification
  • Libraries available for different programming languages
  • Choice between methods depends on specific application needs and security requirements

Chapter 6: Securing Different Application Types

First: is the application internal or external?
If internal, there’s no need to ask the user to grant access to the application when authenticating to the user, as it’s trusted & the admin that registered it with Keycloak can pre-approve access on behalf of the user. Just turn Consent required off for the client.o

For external (or, third-party) applications, Consent required should be enabled.

Securing web applications

  • Web application security with Keycloak depends on the application’s architecture
  • Key considerations:
    • Traditional server-side vs. Single-Page Application (SPA)
    • Use of REST APIs (part of the app or external)
  • Four main architecture types:
    • Server-side
    • SPA with dedicated REST API
    • SPA with intermediary API
    • SPA with external API
  • Common security practices:
    • Use authorization code flow with Proof Key for Code Exchange (PKCE) extension
    • Avoid Resource Owner Password Credential grant
    • Don’t embed Keycloak login page in an iframe
    • Redirect users to trusted identity provider for authentication
  • Server-side web applications:
    • Use confidential client
    • Configure specific redirect URIs for the client (avoid open redirect!)
    • Leverage ID token for HTTP session
  • SPA with dedicated REST API:
    • Secure like server-side application
    • Use confidential client and HTTP session
  • SPA with intermediary REST API:
    • Most secure way to invoke external APIs
    • Uses Backend for Frontend (BFF) pattern
    • Avoids CORS issues
    • Tokens not directly accessible in browser
  • SPA with external REST API:
    • Simplest but less secure approach
    • Uses public client
    • Tokens exposed in browser
    • Security enhancements:
      • Short refresh token expiration
      • Token rotation
      • PKCE extension
      • Careful token storage
      • Protection against XSS
      • Caution with third-party scripts
  • Best practice: Use authorization code flow with PKCE for all web applications
  • Direct token obtainment by SPA may not be secure enough for highly sensitive applications

Securing native and mobile applications

  • Securing native/mobile apps with Keycloak is more complex than web apps
  • Avoid using Resource Owner Password Credential grant for native/mobile apps
  • Use authorization code flow with PKCE extension for native/mobile apps
  • Three options for native/mobile app authentication:
    • Embedded web view (not recommended)
    • External user agent (user’s default browser)
    • In-app browser tab (on supported platforms)
  • Authentication process:
    1. App opens login page in browser
    2. User authenticates with Keycloak
    3. Authorization code returned to app
    4. App exchanges code for tokens
  • Four approaches for returning authorization code to app:
    • Claimed HTTPS scheme
    • Custom URI scheme
    • Loopback interface
    • Special redirect URI
  • Claimed HTTPS scheme is the most secure option when available
  • Device code grant type useful for input-constrained devices without browsers
  • Device code flow:
    1. App requests device code
    2. User enters code on separate device
    3. User authorizes access
    4. App exchanges device code for tokens
  • Device code flow has time limits for user authorization and token requests

Securing REST APIs and services

  • REST APIs protected by Keycloak require access tokens for authentication
  • Applications obtain access tokens from Keycloak and include them in API requests (as bearer token)
  • REST APIs verify the access token to grant or deny access
  • This approach enables easy creation of public APIs for third-party applications
  • Tokens are particularly useful in microservices architecture
  • Tokens allow propagation of authentication context between services
  • Keycloak supports service accounts using Client Credential grant type
  • Client Credential flow allows clients to obtain tokens using their own credentials
  • Tokens can be used beyond REST APIs, including SASL, gRPC, and WebSocket
  • Services can verify tokens directly or through the token introspection endpoint

Chapter 7: Integrating Applications with Keycloak

Choosing an integration architecture

  • Two main integration styles for Keycloak: Embedded and Proxied
  • Embedded integration:
    • Integration code and configuration are within the application
    • Application communicates directly with Keycloak
    • Requires implementation of OpenID Connect support
    • Changes require application redeployment
  • Proxied integration:
    • Integration layer between application and Keycloak
    • External service handles OpenID Connect requests/responses
    • Application relies on HTTP headers for security data
    • Integration managed outside application boundaries
  • Choosing between styles:
    • No definitive rule for selection
    • Can use both styles in an application ecosystem
    • Proxied style good for legacy code or applications behind reverse proxies/API gateways
    • Embedded style simpler, more self-contained, and offers more control
  • Considerations:
    • Proxied style allows centralized management of Keycloak integration
    • Embedded style doesn’t require managing external services
    • Framework/library support can simplify embedded integration

Choosing an integration option

  • Choosing the right client-side implementation for OpenID Connect can be challenging
  • Key requirements for selecting a good integration:
    • Widely adopted, actively maintained, and community-supported
    • Up-to-date with latest OAuth2 and OpenID Connect specifications
    • Aligned with security best practices
    • Good user experience, simple configuration, and deployment
    • Hides complexity from developers while providing secure defaults
    • Avoids vendor lock-in and maintains compliance with standards
  • Recommendation: use solutions native to your technology stack and platform
  • OpenID Connect website offers a list of certified implementations for reference

Avoid implementing integrations yourself.

Chapter 8: Authorization Strategies

  • Authorization systems determine if a user can access and perform actions on a resource
  • Key questions in authorization:
    • User identity
    • User-associated data
    • Resource access constraints
  • Keycloak, as an identity provider, issues tokens containing user and authentication context information
  • Applications can use token information to implement various access control mechanisms
  • Two main authorization patterns:
    • Application-level enforcement (declarative or programmatic) — most common
    • Centralized authorization (delegating decisions to an external service)
  • These patterns can be used together in applications (not mutually exclusive)
  • Keycloak offers flexibility in:
    • Exchanging information for application-level resource protection
    • Choosing different authorization patterns for managing access constraints
  • Keycloak supports various access control mechanisms and authorization patterns

Using RBAC

  • RBAC (Role-Based Access Control) is a widely used access control mechanism
  • Protect resources depending on whether the user is granted a role
  • Keycloak supports role management and propagation through tokens
  • Roles represent user responsibilities in an organization or application context
    • E.g. user is given administrator role so they can do anything in your app. Or ‘people-manager’ role, so they are allowed to access and perform actions on resources related to their subordinates
  • Two categories of roles in Keycloak: realm roles and client roles
    • Realm roles are defined at the realm level and span multiple clients
      • Usually represents the user’s role within an organization
    • Client roles are specific to a single client
      • So they depend on the semantics used by the client
  • Choose between realm and client roles based on the role’s scope.
    • If it spans multiple clients in the realm while having the same meaning, a realm role makes sense.
    • If only a specific client is supposed to interpret the role, a client role makes more sense.
  • Avoid role explosion by creating roles carefully and considering their scope
    • Roles should not be used for fine-grained authorization
  • Keycloak allows granting roles to groups, reducing individual role management
  • Composite roles in Keycloak chain other roles together
    • Use composite roles cautiously to avoid performance and manageability issues
  • Groups with assigned roles are recommended over composite roles
  • Role modeling impacts token size; tokens should contain minimal necessary roles
  • More roles increase system complexity and maintenance difficulty
  • Consider maintainability and performance when implementing RBAC in Keycloak

Using GBAC

  • Group-Based Access Control
  • Groups organize users based on business units or roles
  • Groups and roles are separate concepts in Keycloak
    • Roles can be assigned to groups, simplifying permission management
    • Groups in Keycloak are hierarchical
  • Group information is not automatically included in tokens
    • Protocol mappers are needed to include group information in tokens
  • How to include group information in tokens:
    • Create a client (e.g., myclient)
    • Create a user (e.g., alice)
    • Add a Group Membership mapper to the client’s dedicated client scope
    • Configure the mapper with a name, type, and token claim name
  • How to create and manage groups:
    • Create a group (e.g., Project Management Office)
    • Add users to the group
  • Can use the evaluation tool to check if group information is included in tokens
  • Group information appears as a “groups” claim in the generated token

Using OAuth2 Scopes

  • Keycloak is an OAuth2 authorization server
  • OAuth2 involves two main types of applications: clients and resource servers
    • Clients receive access tokens to act on behalf of users, limited by scopes based on user consent
    • Resource servers consume access tokens to determine client access to protected resources
  • OAuth2 scopes are based on user consent (ideal for third-party API integrations)
  • OAuth2 scopes protect user information rather than regular resources
  • OAuth2 scopes protect systems from clients, while RBAC protects systems from users
  • OAuth2 scopes check if a client can perform actions on behalf of a user (delegation use case)
  • By default, Keycloak clients don’t ask for user consent
    • In enterprise settings, client access is based on permissions granted by system administrators
    • Enterprise clients focus on user authentication, with access scope defined by roles, groups, or attributes
  • OAuth2 scopes are most suitable for allowing third-party access to user information through APIs

Using ABAC

  • ABAC (Attribute-Based Access Control) uses information from Keycloak authentication tokens
  • Tokens contain user and client information, as well as other authentication context details
  • Any information in tokens can be used for authorization
  • ABAC is flexible and supports fine-grained authorization
  • Token-based authorization involves introspecting tokens and using claims to make access decisions
  • Roles are an example of token claims used for access control
  • Applications can use any claim in a token to enforce access
  • Keycloak’s protocol mappers allow customization of claims and assertions in tokens for each client
  • ABAC is flexible but can be challenging to implement and manage
  • Keycloak enables mapping of any desired information to tokens for application-level access control

Using Keycloak as a centralized authorization server

  • Traditional authorization strategies are often tightly coupled with application code
  • Changes in security requirements typically require code modifications and redeployment
  • Centralized authorization externalizes access management from applications
  • Keycloak can act as a centralized authorization service through Authorization Services
  • Keycloak Authorization Services is based on policies associated with protected resources
  • It leverages ABAC for fine-grained authorization
  • Provides out-of-the-box policies for different access control mechanisms
  • Allows control over specific actions and attributes of protected resources
  • Uses token-based authorization to reduce additional network calls
  • Supports incremental authorization for obtaining new permissions as needed
  • Overcomes the issue of multiple round trips for access decisions
  • Enables applications to implement fine-grained authorization without coupling to specific mechanisms
  • Managed through Keycloak administration console and REST API

Chapter 9: Configuring Keycloak for Production

  • Configuring Keycloak for production involves several key aspects:
    • Setting the hostname
    • Enabling TLS
    • Configuring a database
    • Enabling clustering
    • Configuring a reverse proxy
  • Setting the hostname:
    • Configure frontend, backend, and admin URLs
    • Use hostname or hostname-url options
    • Ensures consistent security domain and issuer
  • Enabling TLS:
    • Use HTTPS for all Keycloak communication
    • Configure using a Java KeyStore or PEM files
    • Set TLS constraints on a per-realm basis
  • Configuring a database:
    • Use a production-grade database (e.g., PostgreSQL, MySQL)
    • Set db, db-url-host, db-username, and db-password options
    • Consider connection pool size settings
  • Enabling clustering:
    • Run multiple Keycloak instances for high availability
    • Use distributed cache (Infinispan)
    • Configure cache owners and sizes
  • Configuring a reverse proxy:
    • Provides public access point and load balancing
    • Key requirements: TLS termination, load balancing, session affinity, forwarding headers
    • Example configuration using HAProxy
  • Testing the environment:
    • Verify load balancing and failover
    • Check frontend and backchannel URLs in OpenID Discovery document

Chapter 11: Authenticating Users

  • Authentication flows in Keycloak:
    • Define sequential steps for user and client authentication
    • Can be customized and extended to meet specific requirements
    • Include browser flow, direct grant flow, and client authentication
      • Browser flow: related to how end users authenticate using a browser. All steps from this flow is executed when you’re authenticating users through the browser.
  • Configuring authentication flows:
    • Can duplicate existing flows as templates
    • Hierarchical structure with executions and subflows
    • Executions can be marked as REQUIRED or ALTERNATIVE
    • Remember to bind the flow definition to the flow you want
  • Password authentication:
    • Default method in Keycloak
    • Uses PBKDF2 hashing algorithm for security
    • Can be enhanced with password policies
  • Password policies:
    • Can be added to control various aspects of password management
    • Examples: character requirements, length, expiration, and blacklists
  • Resetting user passwords:
    • Can be done by administrators or users
    • Options include temporary passwords and required actions when the user logs in
  • One-Time Passwords (OTP):
    • Adds a second factor for authentication
    • Supports TOTP and HOTP algorithms
    • Can be set up using FreeOTP or Google Authenticator apps
  • Enforcing OTP usage:
    • Can be optional or required for all users
    • Configurable through authentication flow settings
  • Web Authentication (WebAuthn):
    • Provides stronger security than OTP
    • Uses asymmetric key cryptography
    • Supports various security devices and biometrics
  • Implementing WebAuthn:
    • Can be used for 2FA, MFA, or passwordless authentication
    • Requires creating a custom authentication flow
  • Strong authentication:
    • Typically involves 2FA or MFA
    • Can include biometric verification
    • Keycloak supports step-up authentication for increased security levels

Chapter 12: Managing Tokens and Sessions

  • Keycloak is a centralized authentication and authorization service, as well as a session and token management system
  • Sessions
    • Keycloak creates user sessions (SSO sessions) and client sessions
    • Session lifetime can be configured for both SSO and client sessions
    • Administrators can manage active sessions at realm, client, and user levels
    • Sessions can be expired prematurely through the administration console
    • Keycloak uses cookies (KEYCLOAK_IDENTITY) to track user sessions in browsers
  • Tokens
    • Tokens (ID, access, and refresh) are bound to sessions and have their own lifetimes
    • ID and access token lifespans can be configured globally or per-client
    • Refresh token lifetimes are based on client session settings
    • Refresh token rotation can be enabled to enhance security
    • Tokens can be revoked using various methods, including not-before-revocation policy and session expiration
    • Token revocation helps maintain security and conserve server resources
    • Keycloak provides a token revocation endpoint based on RFC 7009

Chapter 14: Securing Keycloak and Applications

  • Securing Keycloak for production environments
    • Encrypt all communication to/from Keycloak using TLS
    • Configure a fixed hostname for Keycloak
    • Regularly rotate signing keys
    • Keep Keycloak and its dependencies updated
    • Use an external vault for storing secrets
    • Protect Keycloak with a firewall and intrusion prevention system
  • Securing the database
    • Protect with a firewall
    • Enable strong authentication and access control
    • Encrypt data in transit and at rest
    • Secure database backups
  • Securing cluster communication
    • Use a firewall to protect cluster traffic
    • Enable authentication for cluster nodes
    • Encrypt cluster communication
  • Securing user accounts
    • Implement strong password policies
    • Enable brute-force protection
    • Limit stored personal information
    • Protect user data in the database
  • Securing applications
    • Implement proper authentication and authorization
    • Protect against common web vulnerabilities
    • Keep applications and dependencies updated
    • Encrypt sensitive data
    • Implement logging and monitoring
    • Use firewalls and Web Application Firewalls (WAFs)
  • OAuth 2.0 and OpenID Connect best practices
    • Use state, nonce, and PKCE parameters
    • Follow OAuth 2.0 security best current practices
    • Consider OAuth 2.1 and Financial-Grade API (FAPI) profiles
  • Keycloak client configurations for security
    • Enable consent for third-party and native applications
    • Use client authentication when possible
    • Avoid deprecated flows (implicit and resource owner password)
    • Use exact match for redirect URIs
    • Prefer ECDSA over RSA for signing algorithms
    • Configure appropriate token lifespans

Liked these notes? Join the newsletter.

Get notified whenever I post new notes.