Skip to content

OAuth 2.0

  • RFC 6749 – OAuth 2.0 Authorization Framework
  • RFC 7636 – Proof Key for Code Exchange (PKCE)
  • RFC 8628 – Device Authorization Grant
  • RFC 7662 – Token Introspection
  • RFC 7009 – Token Revocation
  • RFC 7523 §2.2 – JWT Profile for OAuth 2.0 Client Authentication
  • RFC 7517 – JSON Web Key Sets
  • RFC 8414 – Authorization Server Metadata
  • RFC 9449 – OAuth 2.0 Demonstrating Proof of Possession (DPoP), opt-in
  • RFC 9700 – OAuth 2.0 Security Best Current Practice
Flow ID Name Description
authorization_code Authorization Code Standard redirect-based authorization
authorization_code_pkce Authorization Code + PKCE Authorization code with code verifier/challenge
client_credentials Client Credentials Machine-to-machine issuance with independent selectors for client_secret_basic or private_key_jwt authentication and Bearer or DPoP access-token protection
refresh_token Refresh Token Token renewal without re-authorization
implicit Implicit Grant (Legacy) RFC 6749 §4.2: access token in the redirect fragment, no refresh token. RFC 9700 §2.1.2 says clients SHOULD NOT use it
device_code Device Authorization RFC 8628 grant for TVs, CLIs, and other input-constrained devices; person authorizes on a second device
token_introspection Token Introspection Validate and inspect active tokens (RFC 7662)
token_revocation Token Revocation Invalidate tokens (RFC 7009)

The implicit grant is implemented for the public public-app client so the fragment response can be compared with authorization code + PKCE. RFC 9700 §2.1.2: “Clients SHOULD NOT use the implicit grant.” The authorization server returns access_token and token_type in the redirect fragment and does not issue a refresh token. Confidential clients receive unauthorized_client. grant_type=implicit is not accepted at the token endpoint.

Resource Owner Password Credentials (grant_type=password) is not implemented. RFC 9700 §2.4: “The resource owner password credentials grant MUST NOT be used.” Use authorization code + PKCE instead.

  • Authorization Code Flow Demo – Complete redirect flow with consent
  • PKCE Flow Demo – PKCE challenge/verifier exchange
  • Client Credentials Demo – Independently select shared-secret or browser-held private_key_jwt authentication and Bearer or DPoP token protection
  • Token Refresh Demo – Refresh grant with token rotation
  • Implicit Grant Demo – Public client receives an access token in the redirect fragment; no refresh token
  • Device Authorization Demo – Device displays a user_code; Looking Glass opens the Protocol Showcase sign-in window at /oauth2/device
Path Methods Purpose
/oauth2/authorize GET, POST Authorization endpoint (response_type=code or token)
/oauth2/device/authorize POST RFC 8628 device authorization endpoint
/oauth2/device GET, POST RFC 8628 verification URI (user enters user_code)
/oauth2/token POST Token endpoint
/oauth2/introspect POST Token introspection
/oauth2/revoke POST Token revocation
/oauth2/demo/users GET List demo users
/oauth2/demo/clients GET List demo clients
/oauth2/demo/caep/revoke-subject POST Demo CAEP subject revocation (SSF receiver; not RFC 7009)
/.well-known/oauth-authorization-server/oauth2 GET RFC 8414 authorization server metadata
/oauth2/demo/clients/machine-client-pkjwt/jwks POST Register the demo client’s public JWKS

The Client Credentials Looking Glass flow can authenticate with a signed JWT instead of a shared client secret. Select private_key_jwt in the flow configuration, run the flow, then choose client assertion in the token inspector to inspect the real JWT sent to the token endpoint. Open the Client Credentials flow.

The browser generates an RS256 key with WebCrypto. Its private CryptoKey is non-extractable and never leaves the client; only the public JWK is registered with the demo authorization server. The start-demo response returns a 256-bit owner capability; full session access, WebSocket streaming, and public-key registration require it. Registration is active-session-only and atomically one-shot. It creates the real session-isolated client_id (there is no seeded private_key_jwt client), expires that registration after 10 minutes, and returns the authorization server’s exact token endpoint. Split deployments therefore use the same aud value the server validates, and concurrent demonstrations cannot replace each other’s keys. The server also supports registered ES256 (P-256) and EdDSA (Ed25519) client keys.

ProtocolSoup applies this deterministic RFC 7523 profile:

  • client_id is required in the request body and must exactly equal both iss and sub.
  • aud must contain the exact configured token endpoint URL. A trailing slash changes the value and fails validation.
  • iat, exp, and jti are required; optional nbf is enforced. RFC 7519 fractional NumericDate values retain nanosecond precision. The assertion lifetime is at most 300 seconds, with 60 seconds of clock-skew tolerance.
  • jti is single-use through the complete acceptance window, including the 60-second expiration-skew allowance. Development and tests use an in-memory reservation store. Demo and production deployments require shared Redis and perform one atomic SET NX reservation keyed by a hash of (client_id, jti); production Redis connections must use rediss://. Replay-store failures fail closed with server_error; assertion validation failures continue to use generic invalid_client responses.
  • Allowed algorithms are RS256, ES256, and EdDSA. The registered key type must be RSA, EC P-256, or Ed25519 respectively. If key_ops is present it must permit verify, and the client registration must select private_key_jwt. RSA moduli must be odd and at least 2048 bits; exponents must be odd, at least 3, and safely representable by the verifier.
  • Static JWKS keys are checked before an optional HTTPS jwks_uri. Remote retrieval bypasses environment proxies, rejects IANA special-use IPv4 and IPv6 addresses during both DNS validation and dialing, applies one 3-second deadline to DNS, connection, TLS, and response reading, rejects redirects, caps responses at 64KB, caches for 10 minutes, coalesces concurrent initial and forced fetches, and rate-limits refreshes caused by unknown kid values. Unsupported or malformed unrelated remote keys are ignored when at least one usable verification key remains.
  • Static, remote, and demo JWKS registrations reject every private JWK member: d, p, q, dp, dq, qi, oth, and k. Rejected registration captures redact those values before Looking Glass persistence.
  • Assertion validation failures return the same invalid_client description. Specific reasons appear only in owner-visible Looking Glass history.

This method is intentionally scoped to client_credentials. JWT Bearer authorization grants (RFC 7523 §2.1), client_secret_jwt, mTLS, and certificate-in-header key delivery are separate mechanisms and are not enabled by this flow.

The token endpoint accepts an optional DPoP proof header (htm=POST, htu the exact /oauth2/token URL). When present and valid, the issued access token is bound to the proof’s key: it carries a cnf.jkt claim (the RFC 7638 thumbprint of the proof’s public key) and the response’s token_type is DPoP instead of Bearer. Absent that header, every existing Bearer flow is unchanged – DPoP is opt-in, never mandatory, on this endpoint.

  • For the authorization_code grant, a DPoP-bound access token issued to a public client also binds the issued refresh token to the same key (RFC 9449 Section 5). A subsequent refresh_token request from that client must re-present a matching proof; a mismatched or missing proof is rejected with invalid_grant. A refresh token issued without DPoP does not gain binding on refresh even if a proof is later presented. A confidential client’s refresh token is never bound this way, even when the token request that issued it carried a valid proof: it is already sender-constrained by its client authentication credentials, per the spec’s own carve-out – only its access token gets cnf.jkt.
  • The DPoP header field itself must not repeat: a request carrying more than one is rejected outright rather than silently using the first one and ignoring the rest.
  • Proof validation rejects alg: none, symmetric algorithms, a jwk header carrying private key material, an htu/htm mismatch, a stale or future iat (60-second window), and a replayed jti – replay-store outages fail the request closed with server_error, never open.
  • A server-provided nonce challenge (DPoP-Nonce response header, use_dpop_nonce error) is available but off by default, enabled via SHOWCASE_DPOP_NONCE_REQUIRED. See Environment Variables.
  • Only JWT access tokens can be bound. Opaque/reference access token binding is a separate mechanism this platform does not implement.
  • The RFC 8414 metadata endpoint lists the accepted DPoP proof algorithms under dpop_signing_alg_values_supported (RFC 9449 Section 5.1).

Run the Client Credentials flow in Looking Glass and select DPoP under Access-token protection to see a real browser-generated ES256 key produce a proof. The resulting bound token is rendered distinctly (amber, lock icon) from a plain Bearer token in the token inspector. Client authentication is a separate selector, so all four real combinations of client_secret_basic or private_key_jwt with Bearer or DPoP can be inspected without switching flows.

RFC 8414 requires an HTTPS issuer. Because protocol routes are mounted at the origin root, SHOWCASE_BASE_URL must be a pathless HTTPS origin in production (for example https://as.example, not https://as.example/base or a trailing slash). The metadata endpoint returns 503 when it cannot derive that issuer. Loopback HTTP remains available for the executable local demo, but it is not published as RFC 8414 metadata.

  • PKCE code_challenge and code_verifier alignment
  • Token claims: iss, sub, aud, exp, iat, scope
  • Introspection response: active, token_type, scope. Resource servers may authenticate by presenting the same access token as Authorization: Bearer (RFC 7662 §2.1).
  • Revocation: subsequent introspection returns active: false
  • Device code: polling interval, authorization_pending vs slow_down
  • private_key_jwt: assertion claims, signature algorithm/key binding, and the replay-protection event
  • DPoP: proof claims (htm, htu, iat, jti), bound token’s cnf.jkt, token_type: DPoP, and refresh-token binding surviving rotation