OAuth Best Practices
OAuth 2.0 is a framework, not a turnkey protocol, so implementers must make dozens of security-relevant decisions themselves. Getting those decisions wrong is the leading cause of real-world OAuth vulnerabilities, from authorization code interception to token leakage via referrer headers or browser history. The practices below, largely codified in RFC 9700 (the OAuth 2.0 Security Best Current Practice), apply to almost every deployment regardless of grant type or client platform.
Cricket analogy: Just as the laws of cricket leave field-placement strategy to the captain, OAuth leaves security choices to implementers, and a captain like Rohit Sharma who sets a sloppy field concedes runs the way a sloppy OAuth client concedes tokens.
Always Use PKCE and the State Parameter
RFC 9700 now mandates Proof Key for Code Exchange (PKCE) for every authorization code flow, not just public clients on mobile and single-page apps. The client generates a random code_verifier, derives a code_challenge via SHA-256, sends the challenge in the authorization request, and later presents the original verifier at the token endpoint so the authorization server can confirm the code redemption came from the same party that started the flow. Separately, the state parameter — a unique, unguessable value tied to the user's session — must be generated per request and validated on return to block cross-site request forgery attacks that trick a victim into completing another attacker's authorization flow.
Cricket analogy: PKCE is like a bowler and wicketkeeper agreeing on a secret signal before the over starts, so if someone from outside the field tries to fake a run-out call, like in a contested Ashes appeal, the umpire can check the signal matches.
GET /authorize?response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=openid%20profile%20orders.read
&state=af0ifjsldkj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256 HTTP/1.1
Host: auth.example.com
# Later, at the token endpoint the client redeems the code and
# proves possession of the verifier that produced the challenge:
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&client_id=s6BhdRkqt3
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkRFC 9700 (2025) deprecates the implicit grant entirely and requires PKCE for all authorization code clients, public and confidential alike, because PKCE protects against authorization code injection regardless of whether the client can also keep a client secret.
Secure Token Storage and Transmission
Access tokens and refresh tokens are bearer credentials: whoever holds them can use them, so where and how they're stored matters as much as how they're issued. For browser-based apps, storing tokens in localStorage or sessionStorage exposes them to any script that runs on the page, so a single cross-site scripting bug anywhere in the app's dependency tree can exfiltrate every active session; httpOnly, Secure, SameSite=Lax cookies scoped to a narrow path are far safer because JavaScript cannot read them. For server-side and native apps, tokens should be encrypted at rest, transmitted only over TLS 1.2+, and access tokens kept short-lived (minutes to a couple of hours) with refresh token rotation so a leaked refresh token has a limited blast radius and reuse can be detected.
Cricket analogy: Keeping a token in localStorage is like leaving the dressing-room door unlocked during a Test at Lord's — anyone who slips past reception, like a rogue photographer, can walk off with the team's playing strategy notes.
Never store OAuth access or refresh tokens in localStorage or sessionStorage for browser-based applications. A single XSS vulnerability anywhere in your JavaScript dependency chain gives an attacker a trivial way to read and exfiltrate them. Use httpOnly, Secure cookies or the Backend-for-Frontend (BFF) pattern instead.
Redirect URI Validation and Scope Minimization
Authorization servers must validate redirect_uri against an exact, pre-registered value — not a prefix match, not a wildcard subdomain match — because any looseness here becomes an open redirect that lets an attacker redirect the authorization code or token to a server they control. Clients should likewise request the narrowest set of scopes that the current operation needs rather than a broad, standing grant; a reporting dashboard that only reads order totals should request orders.read, not orders.write or admin scopes it will never use. Combining tight redirect URI matching with least-privilege scoping and, where supported, sender-constrained tokens (DPoP or mutual TLS) dramatically reduces what an attacker can do even if they manage to intercept a code or token.
Cricket analogy: Exact redirect URI matching is like a stadium checking that a ticket's exact seat number, not just its general stand, matches before letting someone in, the way Eden Gardens enforces seat-specific entry during IPL finals to stop scalpers.
- PKCE is mandatory under RFC 9700 for all authorization code clients, public and confidential.
- The state parameter must be a unique, unguessable value validated on every callback to prevent CSRF.
- Never store tokens in localStorage or sessionStorage; prefer httpOnly Secure cookies or a BFF pattern.
- Access tokens should be short-lived; refresh tokens should rotate and be revoked on reuse detection.
- redirect_uri must be validated with exact string matching against a pre-registered value, never prefix or wildcard matching.
- Request the narrowest scopes an operation actually needs rather than broad standing grants.
- Sender-constrained tokens (DPoP or mTLS) limit the damage even if a token is intercepted.
Practice what you learned
1. Per RFC 9700, which clients are required to use PKCE?
2. Why is localStorage discouraged for storing OAuth tokens in a browser app?
3. What is the main purpose of exact redirect_uri matching?
4. What does sender-constraining a token (via DPoP or mTLS) achieve?
5. What is the security benefit of refresh token rotation?
Was this page helpful?
You May Also Like
OAuth 2.0 vs OAuth 1.0
A technical comparison of OAuth 1.0's signed-request model against OAuth 2.0's bearer-token, multi-grant-type design.
Building an OAuth Client
A step-by-step walkthrough of implementing the OAuth 2.0 authorization code flow with PKCE from registration through token exchange.
OAuth Quick Reference
A condensed lookup of OAuth 2.0 grant types, standard endpoints, token parameters, and common error codes for day-to-day implementation work.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics