Practice: OpenID Connect: ID tokens, discovery and logout

Generated by Claude (Fable 5.1) by hand from the identity and integration ops spike, Scout learning center on 2026-10-14.

Main tool: Keycloak and a Python relying party · Keycloak in Docker is a certified OpenID provider you can configure in five minutes and read the logs of, and a 60-line Python relying party with requests and PyJWT shows every request, token and validation step that a framework would hide. The learning center ran this exact path against Keycloak 26.4 and the step outcomes are its outputs; the no-Docker route (node-oidc-provider) ran the same login and is the alternative named in the setup. · Alternatives: node-oidc-provider in process with npm (no Docker; requires PKCE by default and sends the iss parameter); Authlib's client does discovery, PKCE, nonce and ID-token parsing for you once you have done it by hand; a cloud provider's free tenant (Okta Integrator Free Plan, an Entra tenant) for the real thing, covered in the Okta and Entra lesson.

Every step links into the tool's own documentation; links were checked on 2026-10-14 · version 1

Setup (one time)

  • 1. Start Keycloak 26.4 in Docker bound to 127.0.0.1:18080 with a 1 GiB memory cap and a bootstrap admin password of your choosing, and wait for the master realm's discovery document (about 30 to 60 seconds). No-Docker alternative: install oidc-provider with npm in a scratch folder and run the 20-line provider from the lesson's code on 127.0.0.1:19000.

    The practice is the proof the lesson was written from; both providers ran it and the steps below say where they differ.

    Explained in: Keycloak: Getting started with Docker (www.keycloak.org), checked 2026-10-14

    Done when: The discovery document of the provider answers 200 and names an issuer, a jwks_uri and a userinfo_endpoint.

  • 2. Create a realm (learn or a name of yours) with a user alice (email alice@example.test, verified, a password you choose) in a group integration-engineers, and a public client web-pkce: standard flow on, direct access grants and implicit off, PKCE method S256, exact redirect URI http://127.0.0.1:18081/callback, web origin http://127.0.0.1:18081, and a valid post-logout redirect URI. The admin console does it by hand; the admin REST API does it in a short script.

    A public client with PKCE required is what a single-page or mobile app at a customer will be; the realm takes five minutes by hand.

    Explained in: Keycloak: Getting started with Docker (www.keycloak.org), checked 2026-10-14

    Done when: The realm's discovery document answers 200 and the client's settings page shows S256 under the PKCE method.

  • 3. Create a scratch Python virtual environment with requests and PyJWT (crypto extra). The flow can be driven from a browser (read the code off the URL bar) or by a script that submits the login form; the learning center's run did the latter.

    The validation step needs a JWT library with a JWKS client; PyJWT has one.

    Explained in: PyJWT usage: validating tokens and fetching keys from a JWKS endpoint (pyjwt.readthedocs.io), checked 2026-10-14

    Done when: Importing requests and jwt in the environment works.

Steps

  • 1. Fetch the discovery document and note the issuer, authorization, token, userinfo, jwks and end-session endpoints, and code_challenge_methods_supported.

    A client needs only the issuer URL; the discovery document gives it everything else, which is the first thing to show a customer who sends you a list of endpoints by email.

    Explained in: OpenID Connect Discovery 1.0 (openid.net), checked 2026-10-14

    Done when: You can name the six values without the console and S256 is listed.

  • 2. Build the authorization request for web-pkce with response_type=code, scope 'openid profile email', a random state, a random nonce, and an S256 code_challenge; open it and log in as alice.

    state, nonce and PKCE travel together; sending all three once by hand fixes which protects what.

    Explained in: OpenID Connect Core 1.0 (openid.net), checked 2026-10-14

    Done when: The browser lands on the callback URL with code and state; the state matches and the URL may also carry iss.

  • 3. Exchange the code with the code_verifier and no secret; validate the ID token with PyJWT against the provider's JWKS (kid lookup), algorithms pinned, audience web-pkce, issuer from discovery, required claims exp, iat, iss, aud and sub, then compare nonce.

    This is the relying party's one duty; the checklist is section 3.1.3.7 of the Core specification.

    Explained in: OpenID Connect Core 1.0 (openid.net), checked 2026-10-14

    Done when: Validation returns the claims with sub, email alice@example.test and the matching nonce; the learning center's run printed 'id_token VALID' here.

  • 4. Call userinfo with the access token.

    Userinfo is the 'protected resource' every later collection calls; seeing the JSON once makes claim mapping concrete.

    Explained in: Okta: OAuth 2.0 and OpenID Connect overview (developer.okta.com), checked 2026-10-14

    Done when: 200 with sub, email, preferred_username alice and (after the next step) groups.

  • 5. Add a group membership mapper to the client (claim name groups, in the ID token, access token and userinfo), log in again and read the ID token.

    Groups in the token is what most customers want for roles; the mapper is where the provider side of that lives, and the Okta and Entra lesson shows the equivalents.

    Explained in: Keycloak: Getting started with Docker (www.keycloak.org), checked 2026-10-14

    Done when: groups contains integration-engineers in the ID token and in userinfo.

  • 6. Request a client-credentials token for a confidential client without the openid scope (the OAuth practice path's m2m-batch) and call userinfo with it; then repeat with scope=openid in the token request.

    The learning center's first Newman run lost an hour to this; it is the kind of provider rule a lesson should say out loud.

    Explained in: Keycloak: Getting started with Docker (www.keycloak.org), checked 2026-10-14

    Done when: The first call answers 403 and the second 200, and you can state Keycloak's rule in one sentence.

  • 7. Run RP-initiated logout: send the browser to the end_session_endpoint with id_token_hint and the registered post_logout_redirect_uri; then try to reuse the refresh token. (Read, not run by the learning center.)

    Sign-out that also ends the provider session is what customers mean by logout; the id_token_hint is what makes it silent.

    Explained in: OpenID Connect RP-Initiated Logout 1.0 (openid.net), checked 2026-10-14

    Done when: The browser returns to the post-logout URI, the provider's session cookie is gone, and the refresh request answers 400 invalid_grant.

  • 8. Start node-oidc-provider on 127.0.0.1:19000 with the lesson's configuration, fetch its discovery document and list three differences from Keycloak's (for example the iss parameter support, the device endpoint, the claims and algorithms listed). Optionally run steps 2 to 4 against it.

    An integration that assumed the test provider's behaviour is the one that breaks at the customer; comparing two providers is the cheapest inoculation.

    Explained in: node-oidc-provider documentation (github.com), checked 2026-10-14

    Done when: Your note names three concrete differences with the field names, and you can say which features of the specification are optional.

  • 9. Read the back-channel logout specification and write down what your app would need: the registered logout endpoint, storing sid with the session, validating the logout token (signature, iss, aud, iat, events claim, no nonce), and ending the session. (Read only.)

    Back-channel logout is the version that works without the browser; knowing its shape lets you quote the effort on the call.

    Explained in: OpenID Connect Back-Channel Logout 1.0 (openid.net), checked 2026-10-14

    Done when: Your note lists the five items and says which customers' request ('sign out everywhere', deprovisioning) it answers.