Could your OAuth setup quietly be leaking customer data?
OAuth 2.0 is foundational for API security, but subtle implementation mistakes can expose sensitive customer data. RFC 9700 (also known as OAuth 2.0 Best Current Practice) provides concrete, real-world advice to harden OAuth implementations and avoid common vulnerabilities.
This guide translates RFC 9700 into actionable practices for developers building secure, high-performance APIs.
Why RFC 9700 matters for your APIs
OAuth 2.0 offers flexibility, but this comes with risks. Older or insecure flows, such as implicit grants and resource owner password credentials (ROPC), are vulnerable to attacks like token interception or replay. RFC 9700 deprecates insecure methods and strengthens OAuth flows with mandatory security measures like PKCE (Proof Key for Code Exchange).
What exactly are implicit grants and ROPC?
Implicit grants: An OAuth 2.0 flow where access tokens are returned directly in the URL fragment. Designed initially for browser-based JavaScript apps. However, tokens can leak through browser history, referrer headers, or logs. Here’s an example of an implicit grant:
Resource Owner Password Credentials (ROPC): A flow where users directly give their username/password to the client app, which exchanges these credentials for an access token. This exposes user credentials directly to the client app, increasing risk. Here’s an example of ROPC flow.
RFC 9700 explicitly recommends dropping these insecure flows.
The recommended secure flow: Authorization code with PKCE
The authorization code flow is OAuth’s most secure flow. Instead of directly sending tokens, the authorization server first returns an authorization code that the app exchanges for tokens. PKCE enhances this further.
What is PKCE (Proof Key for Code Exchange)?
PKCE is a security mechanism that prevents interception attacks by associating each authorization request with a unique secret (called a code_verifier). The server receives only a hashed version (code_challenge) during authorization, ensuring tokens are delivered securely only to authorized clients. PKCE is mandatory, even for server-side apps, in RFC 9700.
Example authorization request (with PKCE):
code_verifier: Secret randomly generated by client (never shared openly).
code_challenge: SHA-256 hashed version of the code_verifier sent to the server.
code_challenge_method: Hashing algorithm, always use S256.
Exchanging authorization code for tokens (with PKCE)
After the user authenticates, the server responds with an authorization code. The app exchanges this code, including the original code_verifier, for an access token.
Key RFC 9700 recommendations summarized
- Always use Authorization Code flow with PKCE. This prevents token interception.
- Stop using implicit grants and ROPC. Avoids token exposure and direct credential handling.
- Validate redirect URIs strictly. Always require exact matches to prevent redirects to attacker-controlled URLs.
- Use minimal scopes and short-lived tokens. Reduces impact if tokens are compromised.
- Securely handle refresh tokens. Rotate frequently and store securely (HTTP-only cookies, secure storage).
- Confidential clients must use strong auth. Prefer JWT client assertions or mutual TLS (mTLS) instead of client secrets in public environments.
- Detect token misuse. Monitor token replay and enforce uniqueness (jti).
Common developer mistakes (and how to avoid them)
Mistake: Not validating redirect URIs exactly.
Correct approach: Perform strict string equality checks.
Mistake: Using PKCE incorrectly or insecurely (e.g., weak random strings).
Correct approach: Always generate cryptographically strong verifiers (use Node’s crypto library).
Here's a practical, secure JavaScript snippet (using Node.js's built-in crypto library) for generating cryptographically strong PKCE verifiers.
generateCodeVerifier(): Creates a random, high-entropy string using crypto.randomBytes(). This is ideal for secure OAuth PKCE implementations.
generateCodeChallenge(): Hashes the verifier with SHA-256 as required for the PKCE flow. The challenge is safe to transmit publicly in authorization requests.
Edge cases and when not to strictly apply RFC 9700
- Low-risk, internal-only systems might not require all RFC 9700 measures.
- Extremely resource-constrained environments might find cryptographic requirements challenging.
But for customer-facing, sensitive APIs, RFC 9700 practices should be followed rigorously.
Developer checklist for RFC 9700 compliance
- Use Authorization Code flow with PKCE (S256)
- No implicit or password grants
- Strict redirect URI validation
- Minimal, explicit token scopes
- Refresh tokens rotated securely
- Confidential clients use strong authentication
- Access tokens are short-lived
- Token misuse detection (replay, injection, mix-up)
Conclusion: Strengthening OAuth security
RFC 9700 provides essential, actionable guidance that significantly enhances OAuth 2.0 security. By clearly outlining secure flows (Authorization Code with PKCE), explicitly discouraging risky methods (implicit and password grants), and highlighting critical implementation details like strict redirect validation and secure token handling, RFC 9700 helps developers build APIs resistant to real-world OAuth vulnerabilities.
Adopting RFC 9700 ensures:
- Reduced risk of token leaks and interception.
- Prevention of common attacks like token replay, injection, and mix-up.
- Clear, standardized OAuth implementations that are easier to maintain.
For developers serious about protecting customer data and maintaining trust, RFC 9700 is a practical blueprint for secure OAuth.