OAuth 2.1 isn't a finished RFC. Auth0, Okta, and Keycloak are enforcing it anyway.
Inside the gap between an unpublished IETF draft and what identity providers already require in production.
Three of the largest identity providers in production today, Auth0, Okta, and Keycloak, have spent the last two years rolling out enforcement for a specification that, technically, does not yet exist as a finished standard. OAuth 2.1 is still an IETF Internet-Draft. As of March 2026 it sits at draft-ietf-oauth-v2-1-15, its fifteenth revision, with no published RFC number and no fixed date for one. Yet if you have stood up a new OAuth client against any of these providers in the last eighteen months, you have almost certainly been forced to comply with rules that only exist in that draft.
This is not a story about a standard quietly superseding its predecessor. It is a story about vendors converging on a stricter security baseline faster than the standards process that is supposed to define it, and then enforcing that baseline on developers who often do not know the difference between OAuth 2.0 and the draft sitting on top of it. This article is for engineers who got a confusing rejection from their identity provider and want to understand what actually changed, why it changed before the document was finished, and what specifically breaks in production if an integration was built against the older, looser rules.
What OAuth 2.1 actually is, and why it has no RFC number
RFC 6749, the OAuth 2.0 core specification, was published in October 2012. Over the following decade, the working group did not revise it. Instead, the gaps and weaknesses discovered in production use were patched through a sprawling set of companion documents: PKCE in RFC 7636, the security best-practice guidance in RFC 9700, bearer token usage details, threat model updates, and more. By the early 2020s, implementing "OAuth 2.0 securely" meant reading six or seven separate RFCs and knowing which parts of the original spec to deliberately ignore.
OAuth 2.1 is the attempt to collapse all of that accumulated guidance back into a single, internally consistent document. It does not add new grant types or new endpoints. It is best understood as OAuth 2.0 with the unsafe options surgically removed and the safe options made mandatory. Because it consolidates rather than invents, the working group has treated it as a long-running editorial project rather than a single feature release, which is part of why it has stretched across fifteen draft revisions without urgency to ship.
The four changes that actually break existing integrations
Most of OAuth 2.1's text is consolidation: restating existing best practice in one place. Four changes, however, are not just restatements. They remove options that were previously legal, and removing a previously legal option is exactly the kind of change that breaks a client nobody has touched in three years.
1. The implicit grant is gone
The implicit grant returned access tokens directly in the redirect URI fragment, with no client authentication and no code exchange step. It was convenient for browser-based apps in 2012, when the alternative, server-side token exchange, was awkward for a JavaScript-only client. It is also why access tokens used to show up in browser history, proxy logs, and Referer headers. OAuth 2.1 removes it outright. Single-page applications are expected to use the authorization code flow with PKCE instead, running the code exchange from the browser itself with no client secret involved.
2. Resource Owner Password Credentials is gone
ROPC let a client collect a username and password directly and trade them for a token in one request, skipping the redirect-based flow entirely. It defeats the entire point of delegated authorization, since the client sees the raw credential, and it is incompatible with multi-factor authentication by design. OAuth 2.1 drops it. Any integration still using it, typically a legacy first-party mobile app built before federated login was the default, has no direct replacement; the fix is a real authorization-code flow.
3. PKCE is mandatory for every client, not just public ones
Under OAuth 2.0 plus RFC 7636, PKCE was recommended for public clients (apps that cannot keep a secret, like mobile and single-page apps) and optional for confidential clients with a server-side secret. OAuth 2.1 makes it mandatory for all authorization code flows, confidential clients included. The reasoning is that PKCE defends against authorization code interception regardless of whether the client can also hold a secret, and generating a code verifier costs close to nothing. The S256 challenge method is required; the plain method, which sends the verifier unhashed and offers no real protection on its own, is being phased out of every major provider's default configuration even though the draft does not forbid it outright.
4. Redirect URI matching must be exact
OAuth 2.0 allowed loose matching schemes for redirect URIs in some implementations: wildcard subdomains, path prefixes, query string differences treated as equivalent. Every one of those has been used in a documented open-redirect or token-theft attack. OAuth 2.1 requires byte-for-byte exact string matching against a pre-registered URI, with one narrow carve-out for the loopback IP literal used by native and CLI apps during local development, where the port number is allowed to vary.
| Behaviour | OAuth 2.0 | OAuth 2.1 draft |
|---|---|---|
| Implicit grant | Permitted | Removed |
| Resource Owner Password Credentials | Permitted | Removed |
| PKCE for confidential clients | Optional | Mandatory |
| Redirect URI matching | Exact match recommended, loosely enforced | Exact match required, loopback-port exception only |
| Bearer tokens in query strings | Discouraged in guidance only | Disallowed outright |
Why vendors didn't wait for the RFC
Auth0 began defaulting new tenants to PKCE-required authorization code flows well before 2.1 reached its current draft number. Okta's identity engine has shipped redirect-URI exact-matching as the only supported mode for new applications. Keycloak made PKCE effectively mandatory for public clients starting in its 26.x line and tightened it further in 26.4, alongside stricter default redirect validation. None of these vendors waited for IETF consensus, because none of them needed to: the security argument for each change was settled in RFC 9700, the OAuth Security Best Current Practice document, which itself reached RFC status well ahead of 2.1 finishing its own editorial process.
This is the pattern worth internalising. The RFC number is a packaging milestone, not the moment the guidance became real. Best Current Practice documents and security advisories travel faster than the core specification that formally absorbs them, and identity providers, who carry direct liability for token theft on their platform, have every incentive to enforce the practice the moment the threat model is convincing, not the moment a working group closes the last open issue on the document.
“”
DPoP: the part of the stricter baseline that isn't in OAuth 2.1 at all
A separate but related shift gets conflated with OAuth 2.1 constantly, and it is worth pulling apart. Demonstrating Proof of Possession, defined in RFC 9449 and published in September 2023, is not part of the OAuth 2.1 draft. It is an independent, already-finished RFC that solves a problem 2.1 does not touch: what happens after a bearer token is issued.
A standard OAuth bearer token is exactly what the name says: whoever bears it, holds it. If a token leaks from a log file, a misconfigured proxy, or a compromised dependency, anyone holding the raw string can use it until it expires, no further proof required. DPoP changes the mechanics: the client generates a public/private key pair, and every API request carries a signed proof, a DPoP proof JWT, created fresh with that private key, bound to the specific HTTP method and URL being called. The resource server checks that the proof's public key thumbprint matches the one the token was issued against. A stolen token without the matching private key is just an inert string.
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
.
{
"jti": "e1j3V_bKic8-LAEB",
"htm": "POST",
"htu": "https://api.example.com/resource",
"iat": 1750000000,
"ath": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
}Adoption is moving through the same vendor-led path PKCE took. FAPI 2.0, the Financial-grade API profile used across open banking implementations, mandates DPoP rather than treating it as optional. Keycloak 26.4 ships DPoP support directly. Okta and Auth0 both support it for API-bound tokens as of 2026, ahead of any requirement to do so from the core OAuth specification, which still treats it as fully optional.
Refresh token rotation: the pitfall that breaks things weeks later, not on day one
The change most likely to cause a production incident is not PKCE or redirect matching, since both fail loudly, at integration time, with a clear error. Refresh token rotation fails quietly, and often weeks after a client first goes live.
Under the stricter baseline, every time a refresh token is used to get a new access token, the authorization server is expected to issue a brand-new refresh token and invalidate the old one. This is rotation with reuse detection: if the old, already-rotated refresh token is presented again, the server treats it as a signal that the token may have been stolen and copied, and revokes the entire token family, every token descended from that original grant, rather than just rejecting the one request.
The fix is architectural, not a configuration flag: treat refresh-token storage and refresh-token usage as a single-writer operation per token family, typically with a mutex or a queue in front of the refresh call, so that two near-simultaneous requests from the same client never both try to redeem the same refresh token. Multi-tab browser sessions and retrying background workers are the two places this bug actually surfaces in practice.
A practical migration checklist
For teams auditing an existing OAuth integration against where the ecosystem has already moved, the checklist is short and mechanical:
- Confirm every authorization code flow, public and confidential clients alike, generates a PKCE code verifier and sends the S256 challenge, not plain.
- Remove any remaining implicit-grant or ROPC code paths; both are typically legacy code nobody has revisited since the original integration.
- Register exact redirect URIs per environment rather than relying on wildcard or prefix matching, and stop passing access tokens as query-string parameters.
- Audit refresh-token handling for concurrent-use races, particularly in apps with multiple browser tabs or background retry logic.
- Treat DPoP as a forward-looking addition rather than an OAuth 2.1 requirement; adopt it where the identity provider and resource server both support it, especially for any API handling financial or regulated data.
Where this leaves the working group's draft
OAuth 2.1 will eventually become a numbered RFC, and when it does, the change for most teams will be close to invisible, because the vendors that issue and validate the tokens already enforce the substance of it. The lesson generalises beyond OAuth: in any ecosystem where a handful of large implementers control the enforcement points, security guidance becomes a practical requirement the moment those implementers agree it is sound, not the moment a standards body finishes naming it. Reading the draft is still worth doing, because it is the clearest single explanation of why these specific defaults changed. Waiting for the RFC number before treating the rules as real is no longer a safe assumption for anyone running production authentication.
Document workflows that gate access behind an authenticated session inherit every weakness sitting in the token layer underneath them: a signer link protected by a bearer token with no proof-of-possession check is only as trustworthy as whatever process handled that token last. FlowVerify's own signer sessions are built against the stricter baseline described here by default: mandatory PKCE on every authorization code exchange and exact redirect-URI matching, with no implicit-grant or ROPC code paths left over from an earlier integration. The gap between "technically still permitted" and "safe to run in production" is exactly what this article has been about.
Frequently asked questions
Related reading
WebAuthn in 2026: the production explainer for engineers who missed the passkey shift
Most WebAuthn guides were written in 2019, before passkeys existed. This covers the 2026 picture: the concepts that trip teams up, the production decisions the spec doesn't make, and where to start.
You're probably using the wrong Postgres index on your events table
Every SaaS team has an events table. Most put a B-tree index on it and move on. At tens of millions of rows, that default stops being the right call — here's what to reach for instead.
SSE vs WebSockets vs polling: the 2026 decision guide
Most engineers today encounter the SSE vs WebSockets vs polling question through LLM streaming APIs, not stock tickers. This guide covers when each wins and the operational trade-offs comparison articles usually omit.