Skip to content

Authorization Code + PKCE

The most common OIDC flow. Used by every web app, mobile app, SPA, and desktop app that logs a human in. PKCE (Proof Key for Code Exchange, RFC 7636) is mandatory in this library — both because OAuth 2.0 BCP (RFC 9700) and FAPI 2.0 require it, and because there's no plausible deployment in 2026 that benefits from disabling it.

Specs referenced on this page
New to the vocabulary?
  • Authorization code — a one-time, opaque string the OP hands to the RP via a browser redirect. The RP swaps it at /token for the actual tokens.
  • PKCE ("pixie") — a small extra dance with code_verifier / code_challenge that proves "the client redeeming this code is the same one that started the flow." Stops a malicious app from stealing a redirected code. Walked through in detail below.
  • state — a random opaque value the RP sends with the authorize request and re-checks on the callback; CSRF defence for the redirect.
  • nonce — a random opaque value bound into the ID Token; replay defence at the RP.

The full sequence

Parameter glossary

ParameterSent atPurpose
response_type=code/authorizeAsks for the authorization-code grant.
client_id/authorize, /tokenIdentifies the registered RP.
redirect_uri/authorize (and echoed at /token)Where the OP sends the user back. Exact match against the registered list.
scope/authorizePermissions requested. Must include openid for OIDC.
state/authorizeRandom opaque value the RP echoes on callback. CSRF defense for the redirect.
nonce/authorizeRandom value bound into the ID Token's nonce claim. Replay defense.
code_challenge/authorizeBASE64URL(SHA256(code_verifier)).
code_challenge_method/authorizeS256 (the only one this library accepts).
code/authorize responseSingle-use; max-age 60 s by spec, this lib defaults to that.
code_verifier/tokenThe pre-image of code_challenge. The OP recomputes the SHA-256.
grant_type=authorization_code/tokenSelects this grant.
Client auth/tokenOne of client_secret_basic, client_secret_post, private_key_jwt, tls_client_auth, self_signed_tls_client_auth, or none (PKCE-only).

What PKCE prevents

Walk-through: the attack PKCE blocks

Without PKCE, a malicious app on the same device that controls a URI-handler for myapp:// can intercept the authorization-code redirect:

  1. User logs in on the legit RP. OP issues code=abc to myapp://callback.
  2. Malicious app intercepts the redirect (race condition or universal-link spoof) and reads code=abc.
  3. Malicious app posts code=abc to /token and gets tokens.

PKCE binds the code to a secret only the legitimate RP knows:

  1. The legit RP generates a random code_verifier and sends only SHA256(code_verifier) (the code_challenge) to /authorize.
  2. The OP stores code_challenge alongside the issued code.
  3. At /token, the OP requires code_verifier and recomputes the SHA-256.
  4. The malicious app saw the code but never saw the verifier — its /token call fails.

This works even when the RP can't store a client secret (SPA / native).

How this library enforces it

BehaviourWhere
code_challenge_method=plain is rejected — only S256 accepted.internal/pkce
Authorization request without code_challenge is rejected when the client's RequiresPKCE is true (default for public clients, forced for FAPI 2.0).internal/authorize
code_verifier length and char-set are validated against RFC 7636 §4.1.internal/pkce
Mismatch returns RFC 6749 §5.2 invalid_grant at /token (not /authorize).internal/tokenendpoint/authcode.go

Common errors and what they mean

Wire errorCauseWhere to look
invalid_request code_challenge_methodClient sent plainSend S256
invalid_request_uriPAR request_uri expired or already consumedNew PAR request
invalid_grant (at /token)code_verifier doesn't match, or code already used / expiredDon't reuse codes, regenerate
redirect_uri_mismatchThe redirect_uri at /token differs from /authorizeThey must be byte-identical

Run the flow yourself

examples/03-fapi2 runs a FAPI 2.0 Baseline OP that demands PAR + JAR + DPoP + PKCE in one wiring. The OFCS conformance suite drives this exact sequence through ~129 modules in two FAPI plans; OFCS status shows the breakdown.