7 common security pitfalls in OAuth 2.0 implementations

Secure your OAuth 2.0 implementation by avoiding these 7 common pitfalls. Learn best practices for grant types, PKCE, token validation, and secure storage.

Clifford Spielman |

OAuth 2.0 has become one of the most widely adopted frameworks for delegated authorization across modern web and mobile applications. Its flexibility is a major strength, enabling developers to support a wide range of applications, integrate with third-party identity providers, and design scalable, distributed architectures. That same flexibility, however, also means implementations vary widely in quality. Small misunderstandings can create security gaps in places where developers often don't expect them.

Security issues in OAuth 2.0 rarely stem from problems in the specification itself. Instead, they arise from how individual applications interpret or configure OAuth in real-world environments. This article highlights seven common security pitfalls that appear frequently in real-world systems — issues that weaken defenses, undermine trust boundaries, or introduce subtle vulnerabilities that attackers can exploit.

By understanding these potential security pitfalls and how OAuth 2.0 works, developers can strengthen their OAuth 2.0 implementations and ensure they follow well-established best practices.

1. Choosing the Wrong Grant Type for the Scenario

OAuth 2.0 offers several grant types, each intended for a specific kind of client and deployment environment. A common source of problems is using these flows without considering whether the client is public or confidential — that is, whether it can securely store secrets.

A common example is using the Client Credentials grant (client_credentials) for user authentication. Because this flow has no end user, it is only appropriate for machine-to-machine communication. Applying it to a login flow blurs the line between a user and an application and leaves the system with no cryptographic proof of who is actually present.

The Resource Owner Password Credentials (ROPC) grant (password) still appears in some legacy systems, even though it is discouraged. ROPC bypasses the browser-based authorization experience that provides phishing protections and modern authentication methods, such as multifactor authentication, and it unintentionally encourages users to enter their passwords into applications that may not be trustworthy.

Selecting the correct grant type is foundational. Each flow encodes assumptions about the environment and the possible threats it must defend against, and ignoring those assumptions often leads to security issues.

2. Skipping PKCE for Public Clients

Proof Key for Code Exchange (PKCE) began as an extension for mobile apps, but is now required for all public clients, including Single-Page Applications (SPAs), and is widely recommended for other client types as well.

PKCE safeguards the Authorization Code flow (authorization_code) by binding the authorization code to the original client that requested it. Even if an attacker manages to intercept the authorization code, they cannot exchange it for tokens without the client's code_verifier.

Given today's threat landscape, skipping PKCE is one of the most serious errors a public client can make. OAuth 2.1 (currently in draft) codifies this by deprecating older grant types and focusing on Authorization Code + PKCE as the default approach for new applications. PKCE is straightforward to implement, widely supported, and substantially lowers the risk of authorization codes being intercepted and exchanged by an attacker.

3. Redirect URI Pitfalls

A redirect URI is the callback URL an application registers with the authorization server — for example, https://app.example.com/auth/callback. Problems arise when these URIs are either too permissive or incorrectly configured.

Overly permissive redirect URIs may be accepted by some providers but are insecure in practice, especially when they include wildcards, broad domain patterns, or allow both HTTP and HTTPS — giving potential attackers more flexibility to redirect authorization codes to malicious endpoints.

Misconfigured redirect URIs, on the other hand, may be valid or invalid and can break the flow or open the door to unintended redirection behavior. Examples include mismatched callback paths (even small differences such as a single forward slash matter), typos in the URL, and unused registered redirect URIs.

Redirect URIs should be treated as strict allow-lists, matching exactly what the authorization server expects. As has been pointed out, even small inconsistencies can undermine OAuth's protections and create opportunities for token interception.

4. Incomplete or Incorrect Token Validation in APIs

Once an access token reaches an API, proper validation is essential. APIs are responsible for verifying several core properties of every token, and security weaknesses emerge when these checks are skipped or implemented incorrectly. In ASP.NET Core, much of this validation is enabled by default, but it can still be misconfigured or overridden inadvertently.

At a minimum, APIs should validate:

  • Issuer (iss): ensuring the token was issued by the expected authorization server
  • Audience (aud): confirming the token was intended for this API
  • Expiration (exp): rejecting tokens that are expired or outside their valid window
  • Signature: verifying that the token has not been tampered with

OAuth token validation may include additional checks depending on the system's architecture and requirements, but these core fields form the minimum required for an API to enforce its trust boundary.

When APIs omit or weaken these checks, they may inadvertently accept tokens from the wrong tenant, tokens issued for different services, or tokens that are expired or replayed.

Token validation is not optional. It is the mechanism that ensures an application participates correctly in the broader OAuth security model.

In .NET applications, token validation is typically handled by framework components that verify these fields automatically when configured correctly. For a deeper look at how this process works in practice — including examples using JWTs — see Duende Software's library of featured articles.

5. Storing Tokens Insecurely on the Client

Even when OAuth flows are configured correctly, applications can weaken security by storing tokens in an unsafe manner. In browser-based applications, placing tokens in localStorage or sessionStorage exposes them to any script running on the page, including malicious scripts injected through Cross-Site Scripting (XSS) attacks. A script with access to the page can read the tokens and send them to a potential attacker.

A safer approach for web applications is to use Secure and HttpOnly cookies to deliver tokens to APIs. These cookies are not accessible to JavaScript, which reduces exposure to XSS attacks. For SPAs, Duende Software's Backend for Frontend (BFF) approach avoids storing tokens in the browser entirely by having the backend manage access tokens on the application's behalf. On mobile devices, tokens should be stored in OS-provided secure storage, such as the iOS Keychain or the Android Keystore.

Refresh tokens, in particular, require careful handling because they grant long-term access. Wherever tokens are stored, the goal is the same: ensure they are kept in an environment that limits exposure to attackers.

6. Overly Broad Scopes and Excessive Permissions

OAuth scopes determine which resources a client is allowed to access, but they are easy to misuse. A common pitfall is treating scopes as if they are roles or broad access tiers instead of precise, task-specific permissions. When scopes are defined too broadly — or when clients request more access than necessary — the system begins to violate the principle of least privilege.

Overly broad scopes increase the impact of token leakage — situations where an access token is exposed to an unauthorized party. If a leaked token grants wide-ranging permissions, an attacker may be able to call multiple APIs, read or modify high-value data, or perform privileged operations. This risk grows in distributed systems, where a single access token can unlock several downstream services.

A more secure pattern is to keep scopes narrowly defined and to grant only the minimum access required for each scenario. In practice, this means:

  • Designing small, task-specific scopes
  • Requiring clients to request only the permissions needed for the operation they are performing
  • Reviewing scope definitions periodically as APIs and access patterns evolve

This model mirrors platforms such as Microsoft Graph, which uses granular permissions to limit the blast radius of a potentially leaked or misused token.

7. Using Long-Lived Access Tokens

Long-lived access tokens are widely discouraged in the OAuth ecosystem. The longer an access token remains valid, the greater the opportunity for an attacker to steal and reuse it. Most access tokens are self-contained, so APIs will treat them as valid for their entire lifetime.

In contrast, refresh tokens can be revoked. Each time a client uses a refresh token, the Identity Provider has the opportunity to enforce its policies. Instead of long-lived access tokens, a more secure pattern is to pair short-lived access tokens with refresh tokens. While the user remains signed in, the client exchanges the refresh token for new access tokens, limiting the exposure window if an access token is compromised.

Token lifetimes should still be balanced against the application's security requirements and operational needs. Extremely short expiration periods can place an unnecessary load on the identity provider without offering meaningful additional security.

Final Thoughts

OAuth 2.0 offers a powerful and flexible foundation for modern authorization, but that flexibility requires careful implementation. The pitfalls covered in this article are not obscure edge cases — they can and do appear in many real systems, including those that have been in production for years.

By selecting the correct grant type, using PKCE, validating redirect URIs, securely storing tokens, applying least privilege to scopes, enforcing proper token validation in APIs, and keeping access tokens short-lived, developers can avoid many of the most common security gaps in OAuth deployments.

As with any security-sensitive technology, strong implementations come from understanding both the specification and the practical risks that arise as systems evolve. For deeper guidance on identity, token validation, secure application design, and how OAuth 2.0 works in real applications, the resources available at Duende Software offer an excellent path for continued learning.