OAuth 2.0 Implementation Mistakes That Make Your App Vulnerable
OAuth 2.0 appears simple: redirect the user to the provider, receive a code, exchange it for a token, done. The specification is clear. The libraries are mature. And yet, custom OAuth implementations are one of the most common sources of security vulnerabilities in web applications.
I have reviewed OAuth implementations across dozens of applications. The same mistakes appear repeatedly — and most of them are not caught by automated security scanning tools. Here are the mistakes and how to avoid them.
Mistake 1: Not Validating the State Parameter
The state parameter prevents CSRF attacks on the OAuth flow. Without it: an attacker can initiate an OAuth flow, get a valid authorization code, and trick a victim into completing the flow — linking the attacker's account to the victim's session.
How the attack works: the attacker starts the OAuth flow and gets redirected to the callback URL with a valid code. Instead of completing the flow themselves, they send that callback URL to the victim. The victim's browser follows the URL, exchanging the code and linking the attacker's external account to the victim's session.
The fix: generate a random, unguessable state value before starting the OAuth flow. Store it in the session. When the callback returns, verify that the state parameter matches what you stored. If it does not match, reject the request.
Every OAuth library handles this automatically. If you are implementing OAuth manually: this is non-negotiable.
Mistake 2: Leaking Tokens in URLs and Logs
OAuth tokens (especially access tokens) grant access to user resources. If they appear in: URL parameters (visible in browser history, server logs, and referrer headers), client-side JavaScript (accessible through XSS), or application logs (readable by operations staff) — they are compromised.
Common leak vectors: storing the access token in localStorage (accessible via XSS), passing the token as a URL query parameter (logged by servers, proxies, and analytics), and logging full request objects including Authorization headers.
The fix: store tokens in HTTP-only, secure cookies (not accessible from JavaScript), transmit tokens only in HTTP headers (not URL parameters), redact Authorization headers from application logs, and use short-lived access tokens with refresh token rotation.
Mistake 3: Not Validating Redirect URIs Strictly
The redirect URI tells the OAuth provider where to send the user after authorization. If an attacker can manipulate this: they can redirect the authorization code to their own server, exchange it for an access token, and access the victim's data.
Insufficient validation: accepting any URL that starts with your domain (https://yourapp.com.evil.com matches), accepting URLs with path variations (/callback/../admin), or not validating at all (open redirect).
Strict validation: the redirect URI must exactly match a pre-registered value. No wildcards, no pattern matching, no relative paths. Register the exact callback URL with the OAuth provider and validate on your server that the redirect_uri parameter matches exactly.
Mistake 4: Using Implicit Flow in 2026
The OAuth implicit flow (response_type=token) returns the access token directly in the URL fragment. This was designed for SPAs before PKCE existed. It is insecure because: tokens are exposed in the URL (browser history, logs), there is no way to verify the token was intended for your application, and tokens cannot be refreshed (no refresh token in implicit flow).
The correct approach for SPAs in 2026: Authorization Code flow with PKCE (Proof Key for Code Exchange). PKCE adds a cryptographic challenge that prevents authorization code interception. It works in public clients (SPAs, mobile apps) without requiring a client secret.
If your OAuth implementation still uses implicit flow: migrate to authorization code with PKCE immediately.
Mistake 5: Not Verifying Token Audience
When you receive an ID token (in OpenID Connect) or an access token, verify that it was issued for YOUR application. Without audience verification: an attacker obtains a valid token from the same provider (but for a different application) and uses it to authenticate with your application.
The fix: verify the aud (audience) claim in ID tokens matches your client ID. For access tokens: use token introspection to confirm the token was issued for your application.
Mistake 6: Long-Lived Access Tokens Without Rotation
If an access token is valid for 30 days and gets stolen: the attacker has 30 days of access. Revoking the token might not take effect immediately (depending on your validation approach).
Better approach: short-lived access tokens (15-60 minutes) with refresh token rotation. The access token is used for API requests and expires quickly. The refresh token obtains new access tokens and is rotated on each use (each refresh token is valid for exactly one use — the next refresh issues a new refresh token).
If a refresh token is stolen and used: the legitimate user's next refresh fails (their token was already consumed), alerting them to the compromise.
Mistake 7: Not Handling Provider-Side Revocation
Users can revoke access through the OAuth provider's settings (Google Account → Security → Third-party apps). If your application caches the access token and does not check validity: you continue accessing user data after they revoked consent.
The fix: handle 401 responses from the provider (token invalid or revoked). When you receive a 401: clear the stored tokens, notify the user that their connection was disconnected, and prompt for re-authorization.
Mistake 8: Storing Secrets in Frontend Code
OAuth client secrets must never appear in frontend code, JavaScript bundles, or client-side configuration. SPAs do not use client secrets — they use PKCE instead.
I have seen client secrets in: .env files that are bundled into the frontend build, JavaScript configuration objects in the browser, mobile app source code (decompilable), and public GitHub repositories (automatic secret scanning catches some but not all).
If your client secret is exposed: rotate it immediately at the provider. All tokens issued with the old secret remain valid until they expire.
Mistake 9: Insufficient Scope Restrictions
Request only the OAuth scopes you need. If you need to read user email: request the email scope, not full account access. Minimal scopes mean: less damage if tokens are compromised, higher user consent rates (users are more willing to grant limited access), and compliance with privacy regulations (data minimization principle).
Review your scopes periodically: as features change, the scopes you originally requested might include permissions you no longer use. Remove unnecessary scopes.
The Recommendation: Use a Managed Solution
For most applications: do not implement OAuth yourself. Use a managed authentication service (Clerk, Auth0, NextAuth.js, Supabase Auth) that handles these security concerns for you.
These services: implement the latest security practices, update when vulnerabilities are discovered, handle token storage, rotation, and revocation, and are reviewed by security experts.
Custom OAuth implementation makes sense when: you need deep customization that managed solutions do not support, you are building an authentication service yourself, or you have specific compliance requirements that managed solutions cannot meet.
For everyone else: the risk of getting OAuth wrong far exceeds the cost of using a managed solution. Authentication is not where you want to innovate.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.