Practice: OAuth 2.x the current way

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 with curl or Python requests · Keycloak is a free, certified authorization server that supports every grant in the lesson behind plain switches, and curl or a few lines of requests show every request and response of a flow without a framework hiding them. The learning center ran authorization code with PKCE, client credentials, refresh, introspection and revocation this way against Keycloak 26.4, and the step outcomes below are its outputs. · Alternatives: node-oidc-provider (npm, no Docker) runs the same flows in process and refuses a request without PKCE by default; the oauth.com playground walks the authorization code flow in a browser against a demo server; Postman's OAuth 2.0 helper fetches tokens for any grant (the API testing lesson); navikt/mock-oauth2-server gives a throwaway second issuer for tests.

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, in start-dev mode with a bootstrap admin user whose password you choose; wait until the master realm's discovery document answers 200.

    Keycloak is the free, local authorization server that supports every grant in this lesson; the no-Docker alternative is node-oidc-provider, which the OpenID Connect path names.

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

    Done when: curl http://127.0.0.1:18080/realms/master/.well-known/openid-configuration prints a JSON document.

  • 2. Create a realm with one user (alice@example.test with a password of your choosing), a public client web-pkce with authorization code enabled, PKCE method S256 required and the exact redirect URI http://127.0.0.1:18081/callback, and a confidential client m2m-batch with service accounts enabled and a generated secret. The admin console or the admin REST API both work; the learning center's run did it with a short Python script against the REST API.

    One public client for the user grants and one confidential client for the machine grants cover every step below.

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

    Done when: The realm's discovery document answers 200 and lists S256 under code_challenge_methods_supported; the two clients appear under Clients.

  • 3. Create a scratch Python virtual environment with requests and PyJWT (crypto extra), or decide to use curl for every call; keep the client secret in an environment variable, never in a file you might commit.

    The steps are small HTTP calls; either tool works, and the secret-handling rule is part of the lesson.

    Explained in: OWASP OAuth 2.0 Protocol Cheat Sheet (cheatsheetseries.owasp.org), checked 2026-10-14

    Done when: An interpreter in the environment imports requests and jwt, and echo $CLIENT_SECRET prints the secret.

Steps

  • 1. Fetch the realm's discovery document and list grant_types_supported, code_challenge_methods_supported, and the token, introspection, revocation and device authorization endpoints.

    Every grant starts at an endpoint the discovery document names; reading it once replaces guessing URLs from vendor docs.

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

    Done when: You have the five endpoint URLs and can say which grants this server supports without opening the console.

  • 2. Generate a code_verifier (32 random bytes, base64url, no padding) and its S256 code_challenge; build the authorization URL for web-pkce with response_type=code, the exact redirect URI, scope openid, a random state and the challenge.

    PKCE is two lines of code and the thing every public client must do; seeing the challenge in the URL makes the later token call make sense.

    Explained in: RFC 7636: Proof Key for Code Exchange by OAuth Public Clients (datatracker.ietf.org), checked 2026-10-14

    Done when: Opening the URL in a browser shows the realm's login page, and the URL holds code_challenge_method=S256.

  • 3. Log in as alice. The browser lands on the redirect URI (nothing listens there; read the URL bar) with code and state. Check that state equals the one you sent.

    state is the login-flow CSRF check; checking it by hand once is how you remember to check it in code.

    Explained in: RFC 9700: Best Current Practice for OAuth 2.0 Security (datatracker.ietf.org), checked 2026-10-14

    Done when: You have a code and a matching state; a mismatch would mean the response did not answer your request.

  • 4. Exchange the code at the token endpoint with grant_type=authorization_code, the code, the same redirect URI, client_id web-pkce and the code_verifier, with no client secret.

    This is the one call that turns a login into tokens; a public client authenticates with the verifier alone.

    Explained in: RFC 6749: The OAuth 2.0 Authorization Framework (datatracker.ietf.org), checked 2026-10-14

    Done when: The response is 200 with access_token, id_token, refresh_token, token_type Bearer and expires_in.

  • 5. Post the same code again with a wrong verifier (or the right one).

    Code replay is the attack PKCE and single-use codes defend against; Keycloak also detaches the client's session on reuse, so run this step last among the user-token steps.

    Explained in: RFC 7636: Proof Key for Code Exchange by OAuth Public Clients (datatracker.ietf.org), checked 2026-10-14

    Done when: The token endpoint answers 400 with error invalid_grant, and you can say why a code must be single-use.

  • 6. Post grant_type=refresh_token with the refresh token and client_id to the token endpoint.

    Refresh is how a client stays logged in without the user; rotation is the setting a security review will ask about.

    Explained in: RFC 6749: The OAuth 2.0 Authorization Framework (datatracker.ietf.org), checked 2026-10-14

    Done when: A new access token comes back that differs from the first, and you have noted whether a new refresh token came with it (rotation).

  • 7. Request a token for m2m-batch with grant_type=client_credentials and the client id and secret as HTTP Basic authentication. Decode the access token's payload and read azp, aud, typ and preferred_username.

    The machine grant in one call; the absence of a refresh token and the service-account subject are the two things to notice.

    Explained in: RFC 6749: The OAuth 2.0 Authorization Framework (datatracker.ietf.org), checked 2026-10-14

    Done when: The response is 200 with a Bearer token and expires_in, no refresh token, and the payload names the client as azp and a service account as the subject.

  • 8. Introspect the client-credentials token at the introspection endpoint (Basic authentication as m2m-batch), then introspect a copy with its last five characters changed.

    Introspection is how a resource server checks an opaque token and how a JWT's revocation can be noticed; the active false answer for garbage is the shape of every failed check.

    Explained in: RFC 7662: OAuth 2.0 Token Introspection (datatracker.ietf.org), checked 2026-10-14

    Done when: The genuine token introspects as active true with client_id m2m-batch; the corrupted one as active false.

  • 9. Revoke the token at the revocation endpoint and introspect it again; then request a token with a wrong client secret.

    Revocation and client-authentication failure are the two negative paths an integration's tests should cover; both ran this way in the learning center's test run.

    Explained in: RFC 7662: OAuth 2.0 Token Introspection (datatracker.ietf.org), checked 2026-10-14

    Done when: The revoked token introspects as active false and the wrong-secret request answers 401 with unauthorized_client or invalid_client.

  • 10. Enable the OAuth 2.0 Device Authorization Grant on a client in the console, post to the device authorization endpoint, open the verification URI in a browser and enter the user code, and poll the token endpoint at the stated interval until it returns tokens. (Read, not run by the learning center.)

    The device grant is what replaced the password grant for CLIs; running it once makes the 'go to this URL and enter this code' prompt of every cloud CLI legible.

    Explained in: RFC 8628: OAuth 2.0 Device Authorization Grant (datatracker.ietf.org), checked 2026-10-14

    Done when: The device endpoint returns device_code, user_code, verification_uri and interval; after approval the token endpoint returns tokens, and before approval it returns authorization_pending.