OAuth Security Best Practices
OAuth 2.0 has been deployed for over a decade, and attackers have probed every corner of it. The original 2012 specification left many options open, some of which turned out to be dangerous: the implicit flow, the password grant, loose redirect URI matching, and bearer tokens usable by anyone who steals them. The IETF's OAuth 2.0 Security Best Current Practice (RFC 9700) and the consolidating OAuth 2.1 draft capture the lessons, and profiles like FAPI 2.0 define strict configurations for high-risk sectors such as banking.
This page summarizes what modern OAuth deployments should do, the attacks those rules prevent, and newer mechanisms (DPoP, mTLS-bound tokens, PAR) that make stolen tokens far less useful.
TL;DR
- Use authorization code + PKCE for all user flows; don't use implicit or password (ROPC) grants.
- Register exact redirect URIs, validate
state, and validate the issuer (to prevent mix-up attacks). - Keep access tokens short-lived, scope them narrowly, and restrict audience.
- Sender-constrain tokens with DPoP or mTLS, so stolen tokens can't be replayed.
- Rotate refresh tokens with reuse detection, and prefer a BFF for browser apps.
- Use PAR (pushed authorization requests) and consider FAPI 2.0 for high-security APIs.
Quick Example
A DPoP-bound token request and API call (conceptual HTTP):
The access token is bound to the client's key (the cnf.jkt thumbprint). An attacker who steals it can't produce valid DPoP proofs, so the token is useless to them.
Core Concepts
OAuth 2.1 in Brief
OAuth 2.1 consolidates OAuth 2.0 with its security extensions:
- PKCE required for all clients using the authorization code grant.
- Implicit grant removed (tokens in URLs).
- Resource Owner Password Credentials grant removed (apps handling user passwords).
- Exact redirect URI matching.
- No bearer tokens in query strings.
- Refresh tokens for public clients must be sender-constrained or rotated.
Most providers already support these behaviors, so new deployments should follow them today.
Common Attacks and Mitigations
Sender-Constrained Tokens
Bearer tokens work for whoever holds them. Sender-constrained tokens require proof of possession of a key:
- DPoP (RFC 9449): the client creates a key pair and signs a small JWT (a "proof") per request, including the HTTP method, URL, timestamp, and token hash. Tokens are bound to the key's thumbprint. It works in browsers and mobile apps at the application layer.
- Mutual TLS (RFC 8705): tokens are bound to the client's TLS certificate. It's strong, and common for server-to-server and financial APIs, but awkward in browsers.
Pushed Authorization Requests (PAR)
With PAR (RFC 9126), the client POSTs the authorization parameters directly to the authorization server and receives a request_uri. The browser redirect then carries only that reference. Parameters can't be tampered with or leaked via the URL, and the server can authenticate the client before the user interaction begins. JAR (signed request objects, RFC 9101) provides integrity for request parameters.
FAPI 2.0
The Financial-grade API security profile (from the OpenID Foundation) mandates a hardened configuration: authorization code with PKCE, PAR, sender-constrained tokens (DPoP or mTLS), strong client authentication (private_key_jwt or mTLS), and strict validation. Open banking regimes and other high-risk APIs use it, and it's a useful blueprint even outside finance.
Operational Security
- Client registration hygiene: separate clients per app and environment, minimal allowed grant types and scopes, and owners and expiry for credentials.
- Consent governance: restrict which third-party apps users can authorize in enterprise tenants (admin consent), and review granted permissions periodically.
- Monitoring: alert on unusual token volumes, refresh token reuse, logins from new regions, and consent grants to new apps.
- Key management: rotate signing keys (published via JWKS) and client keys, and store them in a KMS or HSM. See secrets management.
- Keep libraries updated: OAuth libraries fix validation bugs regularly.
Best Practices
Follow RFC 9700 Checklist-Style
Treat the Security BCP as a checklist during design and review: PKCE, exact redirects, no implicit or password grants, audience-restricted tokens, refresh token protection, and issuer validation.
Constrain Tokens in High-Risk Contexts
For APIs handling payments, health data, or admin operations, deploy DPoP or mTLS so token theft alone isn't enough to act.
Minimize Token Exposure in Browsers
Use a backend-for-frontend so tokens never reach JavaScript. If tokens must live in the browser, keep them short-lived and in memory, use DPoP with non-extractable WebCrypto keys, and harden against XSS.
Least Privilege Everywhere
Narrow scopes, per-API audiences, short lifetimes, and per-client permissions. Assume any single token or client will eventually leak, and limit what it can do.
Common Mistakes
Still Using the Password Grant
Apps collecting user passwords and exchanging them for tokens (ROPC) bypass MFA, train users to type passwords into third-party UIs, and break federation. Use the authorization code flow.
Wildcard Redirect URIs
https://*.example.com/callback lets any subdomain, including a compromised or user-controlled one, receive codes. Register exact URIs.
Not Validating the Issuer
Clients integrating multiple identity providers without checking the iss parameter are vulnerable to mix-up attacks that route codes to an attacker-controlled provider.
FAQ
What is OAuth 2.1?
A consolidation of OAuth 2.0 with its most important security extensions and best practices: PKCE required, implicit and password grants removed, exact redirect URI matching, and protected refresh tokens. It simplifies OAuth by removing insecure options, and most modern providers already support its requirements.
What is DPoP?
Demonstrating Proof of Possession (RFC 9449): a mechanism binding access and refresh tokens to a client-held key pair. Each request includes a signed proof, so stolen tokens can't be used without the private key. It works at the HTTP layer, which makes it suitable for browsers, mobile apps, and services.
Why is the implicit flow deprecated?
It returned access tokens directly in the browser URL fragment, exposing them to history, logs, referrers, and scripts, with no way to bind them to the client. Authorization code with PKCE provides the same browser compatibility securely.
Do I need FAPI?
Regulated financial APIs (open banking) often require it. For other high-risk APIs, adopting FAPI 2.0's core measures (PAR, sender-constrained tokens, strong client authentication) is a solid hardening path, even without formal certification.
Related Topics
- OAuth — The framework overview
- OAuth Authorization Code with PKCE — The recommended user flow
- OAuth Token Lifecycle — Rotation and revocation
- OpenID Connect — Identity-layer validation
- API Security — Protecting the APIs tokens unlock
- Zero Trust — Proof-of-possession and continuous verification