Learn Keycloak - OpenID Connect Fundamentals
Episode 9 of 31

Learn Keycloak - OpenID Connect Fundamentals

Getting to know OpenID Connect as an identity layer on top of OAuth 2.0: the structure of the ID token as a JWT, standard claims, the main OIDC flows, the UserInfo endpoint, and configuration discovery via well-known and JWKS.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

For the last four episodes you worked with pure OAuth 2.0: tokens, scopes, and consent. Episode 9 opens a new phase — OpenID Connect (OIDC). If OAuth 2.0 answers "who is allowed to access what", OIDC answers a more fundamental question: "who is this user, and how does the application know?" OIDC is the standard answer Keycloak uses as an identity provider.

OIDC: The Identity Layer on Top of OAuth 2.0

OIDC is an identity layer built on top of OAuth 2.0. It doesn't replace OAuth — it adds what was previously missing:

  • Standardized authentication — a standardized way for applications to prove that a user really is who they claim to be.
  • ID token — a dedicated JWT token carrying the user's identity, something pure OAuth 2.0 doesn't have.
  • UserInfo endpoint — a standardized endpoint for fetching the user's profile.
  • Discovery mechanism — public metadata that makes integration easier.

The key behind the scenes: the OAuth 2.0 access token still exists, but now there's a second token called the ID token that serves as proof of authentication.

OIDC Flows

OIDC inherits the OAuth 2.0 flows, each with its own nuance:

  • Authorization Code flow — the recommended flow: the code is exchanged in the backend, and the ID token and access token are issued via the token endpoint. This is the only flow recommended for new applications, especially with PKCE.
  • Implicit flow — the legacy flow that hands over tokens directly via the redirect URI. Not recommended because the token is exposed in the browser URL.
  • Hybrid flow — a compromise: part of the response comes through the authorization endpoint, part through the token endpoint. Used for rare special cases.

A safe rule of thumb: use the authorization code flow with PKCE. The other two flows are only for compatibility.

ID Tokens and the JWT Structure

An ID token is a JWT (JSON Web Token) — three parts, each base64url-encoded and separated by dots:

  • Header — signature algorithm and key ID (alg, kid).
  • Payload — the user's identity claims.
  • Signature — Keycloak's cryptographic signature.

Example ID token payload:

ID token payload
{
  "exp": 1754213000,
  "iat": 1754212700,
  "auth_time": 1754212700,
  "jti": "a3f2c1d0-...",
  "iss": "https://kc.example.com/realms/my-realm",
  "aud": "my-app",
  "sub": "9c4d5e6f-...",
  "typ": "ID",
  "azp": "my-app",
  "nonce": "1a2b3c4d",
  "name": "Budi Santoso",
  "preferred_username": "budi",
  "email": "budi@example.com",
  "email_verified": true
}

See that typ has the value ID — this is the marker that this token is indeed an ID token, not an access token.

ID Token vs Access Token

AspectID TokenAccess Token
PurposeProving user authenticationGranting access to an API
ContentIdentity claimsAuthorization claims and roles
UsageConsumed by the client appSent to the resource server
FormatAlways a JWTJWT or opaque
Distinctive claimssub, nonce, auth_timescope, realm_access, resource_access

The classic mistake: sending the ID token as a Bearer token to an API. The ID token isn't a replacement for the access token — a resource server shouldn't trust a token whose audience is the client application.

Standard Claims and Custom Claims

OIDC defines standard claims that are required or commonly present in the ID token:

  • sub — the unique, permanent user identifier within the realm.
  • iss — the token issuer; it must exactly match the realm's issuer URL.
  • aud — the intended audience, usually the application's client ID.
  • exp and iat — expiration time and issued-at time.
  • auth_time — when the user last completed full authentication.

On top of the standard claims, Keycloak can add custom claims via protocol mappers — e.g. user attributes or roles. This is covered in depth in episode 10.

Validating the ID Token

Accepting an ID token without validation is like letting anyone in. The required steps:

  1. Make sure iss exactly matches the realm's issuer URL.
  2. Make sure aud contains your client ID.
  3. Verify the JWT signature using Keycloak's public key from the JWKS endpoint.
  4. Check that exp hasn't passed and iat isn't unreasonably far in the future.
  5. Match the nonce stored when starting the request.
Fetching the JWKS public keys
curl -s "https://kc.example.com/realms/my-realm/protocol/openid-connect/certs"

protocol/openid-connect/certs returns the public keys in JWK format. In most frameworks, this validation is handled by middleware — but you must understand what's being checked.

The UserInfo Endpoint

Besides the claims inside the ID token, OIDC provides a UserInfo endpoint for fetching the user profile separately:

Calling the UserInfo endpoint
curl -s -H "Authorization: Bearer eyJhbGciOi..." \
  "https://kc.example.com/realms/my-realm/protocol/openid-connect/userinfo"

The response is JSON containing the claims permitted by the approved scopes. Because it's fetched with an access token, the UserInfo response can be richer than the ID token — that's a claims strategy question covered in episode 10.

Discovery and Metadata

OIDC wraps all endpoint addresses in a single public metadata file:

Fetching the OIDC metadata
curl -s "https://kc.example.com/realms/my-realm/.well-known/openid-configuration"
Excerpt of the openid-configuration
{
  "issuer": "https://kc.example.com/realms/my-realm",
  "authorization_endpoint": "https://kc.example.com/realms/my-realm/protocol/openid-connect/auth",
  "token_endpoint": "https://kc.example.com/realms/my-realm/protocol/openid-connect/token",
  "jwks_uri": "https://kc.example.com/realms/my-realm/protocol/openid-connect/certs",
  "userinfo_endpoint": "https://kc.example.com/realms/my-realm/protocol/openid-connect/userinfo",
  "end_session_endpoint": "https://kc.example.com/realms/my-realm/protocol/openid-connect/logout",
  "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"]
}

With this metadata, any OIDC library can configure itself — just give it the issuer URL and the rest is discovered automatically. /.well-known/openid-configuration is the gateway to every integration.

Dynamic Client Registration

OIDC also defines dynamic client registration — a client registers itself with Keycloak via a request without admin intervention. Keycloak supports it at the registration endpoint, but the feature must be explicitly enabled in Realm settings because it opens the door to automatic registration. For strict production environments, leave it off and register clients manually.

Closing

In episode 9 you got to know OIDC: its position as an identity layer on top of OAuth 2.0, its main flows, the JWT structure of the ID token, standard and custom claims, token validation steps, the UserInfo endpoint, discovery metadata, and dynamic client registration.

Key takeaways:

  • OIDC adds identity on top of authorization — the ID token is proof of authentication, not an access grant.
  • The ID token isn't an access token — never send it to a target API.
  • Validation is always required — check iss, aud, exp, the signature, and nonce.
  • Discovery makes integration simple — one issuer URL is enough; other endpoints are found automatically.

In the next episode (episode 10), you'll dissect OIDC claims and the user profile — what standard claims are available, how to add custom attributes via protocol mappers, and when to use the ID token or the UserInfo endpoint.

Learn Keycloak - OpenID Connect Fundamentals | Learn SSO with Keycloak