OAuth 2.1 in Production: Mandatory PKCE, Refresh Token Rotation, DPoP, and Token Exchange Across Service Chains
A practical guide to the protocol almost everyone integrates by copying an example and almost nobody audits: what OAuth 2.1 changes versus OAuth 2.0 and why it isn't cosmetic, PKCE explained from the attack it prevents —authorization code injection— including the downgrade trap that leaves the flow defenseless even when the client implements it correctly, exact redirect_uri matching and why a prefix check is a compromised account, RFC 9207's iss parameter against the mix-up attack, refresh token rotation with family-based reuse detection, including the race condition that logs out legitimate users and the grace window that fixes it, DPoP (RFC 9449) with the proof JWT, the cnf.jkt confirmation, the ath claim, server-issued nonces, and the shared jti cache that makes replay protection real, and token exchange (RFC 8693) for delegation without impersonation using the nested act claim, downscoping, and narrowed audiences. Covers discovery with RFC 8414 and RFC 9728 —the foundation of MCP authorization—, RFC 8707 resource indicators, an honest DPoP versus mTLS comparison, eight recurring mistakes, a production checklist, FAQ, and glossary. With production-ready code in Python, TypeScript, and SQL. 22 September 2026 update: the missing link, the resource server, with the five checks on an access token and the two almost nobody performs (audience and authorization), algorithm confusion with alg:none and RS256 downgraded to HS256 using as the secret the public key the attacker already has, a JWKS cache with a double limit that neither gets stuck after a rotation nor lets a made-up kid become a DoS against the IdP, the at+jwt type from RFC 9068 and why an API that accepts ID tokens is accepting anyone signed in to any application in the tenant, and the choice between local validation and introspection framed as what it really is: a revocation decision, not a performance one. Really logging out, with the honest answer on how long a revoked token lives (until its exp) and the access token lifetime understood as your revocation window, what RFC 7009 actually revokes and why it returns 200 even for tokens that never existed, back-channel logout as the only variant that survives third-party cookie blocking with the three checks specific to a logout token and the sid that saves you from signing everyone out, and a bounded Redis denylist whose entries expire exactly when the token does. And browser applications, with the localStorage/memory/cookie comparison reframed around blast radius, a complete BFF in FastAPI with PKCE, cookie-bound state, tokens that never cross into the browser and a proxy with an origin check, plus the bill the BFF charges: server state, CSRF back on the table, and the SSRF that appears if you don't validate which paths get forwarded. Grounded in RFC 10017 and RFC 9700. September 23, 2026 expansion: client authentication with private_key_jwt and the audience injection vulnerability fixed by draft-ietf-oauth-rfc7523bis (aud equal to the issuer, never the token endpoint, plus the client-authentication+jwt typ), strict server-side validation with a jti cache and zero-downtime key rotation; PAR (RFC 9126) with the correct assertion, require_pushed_authorization_requests and how it differs from JAR; step-up with RFC 9470, acr, auth_time and the max_age that is actually enforceable; native apps and CLIs with 127.0.0.1 loopback per RFC 8252, and device flow as a phishing vector with its mitigations; adversarial tests in pytest; and Prometheus metrics and alerts to detect token theft before the incident.
Verificando acceso...