OAuth 2.1 vs OAuth 2.0, What Changed
TL;DR — OAuth 2.1 (draft, near-final 2022) consolidates years of best practice: PKCE required for all clients, no implicit grant, no resource owner password credentials grant, exact-match redirect URIs, refresh token rotation. If you’re starting fresh, design to 2.1 from day one.
After refresh tokens, the protocol layer. OAuth 2.0 (RFC 6749, 2012) is showing its age. OAuth 2.1 (in draft, expected to finalize 2023) cleans it up.
What’s deprecated in 2.1
Implicit grant. Used to be the SPA pattern. Returned access tokens directly in the URL fragment. Vulnerable to token leakage. Replaced by Authorization Code + PKCE.
Resource Owner Password Credentials (ROPC) grant. Client sends user’s username+password to the auth server. The client now has the password — exact opposite of OAuth’s purpose. Gone in 2.1.
What’s required in 2.1
PKCE for all clients. Proof Key for Code Exchange. Originally for public clients (mobile, SPA); 2.1 requires it for confidential clients too.
How PKCE works:
- Client generates random
code_verifier - Client hashes it:
code_challenge = base64url(sha256(code_verifier)) - Client sends
code_challengewith authorization request - Auth server stores it alongside the issued code
- Client exchanges code for token with
code_verifier - Auth server checks
sha256(code_verifier) == stored code_challenge
Defeats authorization code interception attacks. Always do it.
Exact-match redirect URIs. No wildcards, no pattern matching. Redirect URI sent in token request must exactly match the one registered. Prevents open-redirect attacks.
Refresh token rotation. Refresh tokens are single-use; each refresh issues a new token. Pattern covered in refresh tokens post.
The “right” flow in 2.1
For a web app or SPA:
1. Client generates code_verifier (random 43-128 chars)
2. Client computes code_challenge = base64url(sha256(code_verifier))
3. Client redirects user to:
GET /authorize?
client_id=...
&redirect_uri=https://app.example.com/callback
&response_type=code
&scope=read write
&state=<csrf token>
&code_challenge=<challenge>
&code_challenge_method=S256
4. User authenticates, consents
5. Auth server redirects to:
GET https://app.example.com/callback?code=...&state=<csrf>
6. Client verifies state matches; exchanges code:
POST /token
grant_type=authorization_code
code=...
redirect_uri=...
client_id=...
code_verifier=...
7. Auth server returns:
{access_token, refresh_token, token_type, expires_in}
This is “Authorization Code with PKCE” — the only grant for user-facing flows in 2.1.
What’s left in 2.1
Three grants remain:
Authorization Code (with PKCE): for users. The standard flow.
Client Credentials: for service-to-service. No user. Client authenticates with its own credentials, gets a token.
POST /token
grant_type=client_credentials
client_id=...
client_secret=...
Refresh Token: to renew access tokens. Always rotated.
That’s it. Cleaner than 2.0.
What this means for your code
If you’re implementing an OAuth client in 2022:
- Use PKCE always
- Use Authorization Code + PKCE for users
- Never use Implicit grant
- Never use ROPC
- Verify state to prevent CSRF on the callback
- Use exact redirect URI
If you’re implementing an OAuth server:
- Require PKCE
- Reject Implicit and ROPC grants
- Validate redirect URIs exactly
- Rotate refresh tokens
Most modern libraries default to these. Verify yours does.
Specific library notes
Auth0, Okta, Clerk, WorkOS, Stytch: all default to 2.1 patterns. Just use them.
Spring Security: explicit PKCE flag.
oauth4webapi (JavaScript): designed for 2.1 from the start.
Older libraries: may default to 2.0 patterns. Audit.
Calling APIs with the token
Once you have an access token:
GET https://api.example.com/me
Authorization: Bearer <access_token>
Standard. The token format may be JWT or opaque; depends on the auth server.
For multi-resource scenarios (one token, multiple APIs), use the aud claim in JWT to identify the intended audience. Each API verifies it’s named.
Common Pitfalls
Implicit grant in 2022. Don’t. Use Authorization Code + PKCE.
ROPC grant. Don’t. User credentials should never touch the OAuth client.
Wildcard redirect URIs. Open-redirect attack vector. Exact match.
Skipping state parameter. CSRF on the callback.
Storing client_secret in SPA. Public clients have no secret. PKCE is the substitute.
Skipping HTTPS. OAuth tokens in plaintext = compromised.
Long-lived access tokens. Defeats rotation. Short access + refresh.
Wrapping Up
OAuth 2.1 = OAuth 2.0 minus the bad parts. Use Authorization Code + PKCE; rotate refresh tokens; exact redirect URI. Monday: rate limiting algorithms.