Safeguard
Security Guides

OAuth 2.0 Security Best Practices (2026)

OAuth 2.0 is safe when you follow the current security BCP and dangerous when you follow a decade-old tutorial. Here is what RFC 9700 requires in 2026: PKCE everywhere, exact redirect matching, and sender-constrained tokens.

Daniel Osei
Security Researcher
6 min read

OAuth 2.0 is the standard for delegated authorization, letting an application act on a user's behalf without handling their password. It is also frequently implemented against tutorials written a decade ago, and the difference between the two is where breaches live. In January 2025 the IETF published RFC 9700, Best Current Practice for OAuth 2.0 Security (BCP 240), which consolidates years of real-world attack experience and formally deprecates several flows that used to be considered acceptable. If your OAuth implementation predates it, or was copied from something that did, it is very likely doing at least one thing the current standard tells you not to.

The short answer for 2026: use the Authorization Code flow with PKCE for every client type, match redirect_uri exactly, defend against mix-up and CSRF, keep access tokens short-lived, rotate refresh tokens, and prefer sender-constrained tokens. Below is what each of those means in practice.

Use Authorization Code with PKCE for everything

The Implicit grant and the Resource Owner Password Credentials grant are deprecated by RFC 9700 and should not be used in new applications. The Implicit grant returned tokens directly in the URL fragment, where they leak through history and referrers; ROPC hands the user's password to the client, defeating the entire point of delegation.

The correct flow for all clients, including single-page and mobile apps, is Authorization Code with PKCE (Proof Key for Code Exchange, RFC 7636). The client generates a random code_verifier, sends its hash as the code_challenge on the authorization request, and proves possession of the verifier when exchanging the code. An attacker who intercepts the authorization code cannot use it without the verifier.

// Generate a PKCE verifier and S256 challenge
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
const challenge = base64url(new Uint8Array(digest));
// Store `verifier` for the token exchange; send `challenge` on the auth request.
GET /authorize?response_type=code
  &client_id=acme-web
  &redirect_uri=https://app.example.com/callback
  &scope=openid%20profile
  &state=RANDOM_ANTI_CSRF
  &code_challenge=CHALLENGE
  &code_challenge_method=S256 HTTP/1.1
Host: auth.example.com

Match redirect_uri exactly and defend against mix-up

Open redirect and redirect-manipulation attacks are among the most common OAuth flaws. RFC 9700 requires the authorization server to compare redirect_uri against registered values using exact string matching, not pattern or prefix matching. No wildcards, no "starts-with" checks, no allowing an extra query parameter. A single loose rule here can let an attacker redirect the authorization code to a site they control.

When a client talks to multiple authorization servers, it must also defend against mix-up attacks, where a response from one server is fed to the client as if it came from another. The defense is to verify the issuer of every response, using the iss parameter returned in the authorization response, and to reject anything that does not match the server you started the flow with.

Bind tokens tightly and keep them short-lived

Bearer tokens are, by definition, usable by anyone who holds them, so limit both their lifetime and their portability. Keep access tokens short-lived (minutes, not days) and use refresh tokens with rotation, so each refresh issues a new refresh token and invalidates the old one; a stolen refresh token then becomes detectable when the legitimate client and the attacker both try to use the same one.

Where the ecosystem supports it, move to sender-constrained tokens so a stolen token is useless without the client's key. The two mechanisms are mutual TLS (RFC 8705) and DPoP (RFC 9449). Both bind the token to a key the client must prove it holds on every call, turning a bearer token into something an attacker cannot simply replay.

Finally, always send the anti-CSRF state parameter (or rely on PKCE's binding), validate it on return, and never place tokens in URLs or logs.

OAuth hardening checklist

ControlRequirement
FlowAuthorization Code + PKCE for all clients
DeprecatedNo Implicit grant, no ROPC
Redirect URIsExact string matching, no wildcards
Mix-up defenseVerify iss on every authorization response
CSRFValidate state; rely on PKCE binding
Access tokensShort lifetime, minimal scope
Refresh tokensRotation with reuse detection
Token bindingPrefer DPoP or mTLS sender-constraining

How Safeguard helps

OAuth security spans the code you write and the libraries you rely on to implement it. Safeguard's software composition analysis tracks the OAuth, JWT, and OpenID Connect libraries in your dependency graph and flags versions with known authentication-bypass or token-validation CVEs, with reachability context so you fix the exploitable ones first. Dynamic application security testing exercises your live authorization endpoints for issues like loose redirect handling and missing state validation, while Griffin AI maps findings back to concrete RFC 9700 guidance. Developers run the same checks in CI with the Safeguard CLI, and teams comparing platforms can review the pricing.

Ship OAuth that matches the current standard: get started free or read the documentation.

Frequently Asked Questions

Do single-page and mobile apps still need PKCE?

Yes. RFC 9700 recommends Authorization Code with PKCE for every client type, not just those that can keep a secret. PKCE binds the authorization code to the client that requested it, so an intercepted code cannot be exchanged for tokens by an attacker, which is essential for public clients that cannot hold a client secret.

Why is the Implicit grant deprecated?

The Implicit grant returned access tokens directly in the URL fragment, where they leak through browser history, referrer headers, and logs, and it offered no protection against token injection. RFC 9700 deprecates it in favor of Authorization Code with PKCE, which delivers tokens through a back-channel exchange rather than the front-channel URL.

How strict does redirect_uri matching need to be?

Exact string matching against pre-registered values, with no wildcards, prefix rules, or extra parameters allowed. Loose matching is a leading cause of OAuth account takeover because it lets an attacker steer the authorization code or token to a URL they control. Register every exact callback URL your app uses.

What are sender-constrained tokens and should I use them?

They are tokens cryptographically bound to a key the client must prove it holds on every request, using DPoP (RFC 9449) or mutual TLS (RFC 8705). Unlike plain bearer tokens, a stolen sender-constrained token cannot be replayed without the client's key. Adopt them wherever your authorization server and clients support it, especially for high-value APIs.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.