Defending OAuth: Common attacks and how to prevent them
OAuth vulnerabilities can be tricky, but we’re here to help! Learn about common attacks and how to protect your app with simple tips from RFC 9700.
OAuth is everywhere these days—whether you're logging in with your Google account or letting apps access your data through APIs, it's the framework that makes it all happen. But just because it’s widely used doesn’t mean it’s always secure. Like any system, OAuth has its vulnerabilities, and if not properly handled, attackers can take advantage of these weaknesses to gain unauthorized access.
The OAuth 2.0 Security Best Practices (RFC 9700) was written precisely to address common pitfalls and provide valuable insights into implementing OAuth correctly. In our first article for this RFC, we summarized the best practices included in the proposal. In this one, we’ll take a closer look at some of the most common OAuth attacks and explain how they work. We’ll cover things like token theft, code injection, and mix-up attacks, and show you how attackers could exploit them. We will also walk you through some easy and practical ways to protect your app and keep things secure. Whether you’re a developer or just curious about OAuth, this guide will help you understand the risks and how to defend against them.
!! This article assumes you are familiar with basic OAuth terminology (authorization server, grant type, etc). If you need a refresher, check out our Guide to OAuth 2.0. !!
Insufficient validation of redirect URI
In OAuth 2.0, the redirect URI (aka., a redirect URL or callback URL) is the URL to which the user is redirected after they authenticate and authorize the application. This URL should be validated; when this does not happen or is done inadequately, it can lead to security issues.
During the authorization process, attackers can manipulate the redirect_uri
parameter to redirect authorization codes or tokens to their own endpoints, leading to unauthorized access. Here’s how this can happen:
- In OAuth's authorization code grant flow, the client directs the user to the authorization server with a request that includes a
redirect_uri
. - After the user authorizes the request, the server redirects the user back to this URI with an authorization code.
- If the server doesn't validate that the
redirect_uri
matches one of the client's pre-registered URIs, an attacker could craft a request with a maliciousredirect_uri
pointing to their own server. Consequently, the authorization code is sent to the attacker's server, allowing them to potentially hijack the authorization process.
To defend against this attack:
- Validate the redirect_uri: Authorization servers should ensure that the
redirect_uri
in incoming requests exactly matches one of the URIs registered by the client. This prevents attackers from using unregistered or malicious URIs. - Use of exact matching or canonicalization: Implement exact string matching or canonicalization techniques to compare the
redirect_uri
in requests with registered URIs, accounting for potential variations like trailing slashes or case differences. - Require redirect_uri in token requests: When exchanging an authorization code for an access token, require that the
redirect_uri
parameter is included and matches the original URI used in the authorization request. This adds an additional layer of verification. - Avoid wildcard redirect URIs: Do not use wildcard characters (e.g.,
*
) in registeredredirect_uri
values, as they can inadvertently allow unauthorized redirections.
Credential leakage via referer headers
During the authorization process, authorization codes or access tokens may be included in the URL of the redirection response. This sensitive data can be exposed via referer headers or browser history:
- Credential leakage via referer headers: The HTTP Referer header contains the absolute or partial address from which a resource has been requested. When a client application redirects a user to an authorization server, the URL in the request may contain sensitive data. If the user's browser sends this URL as a Referer header in subsequent requests, or if the authorization server's response URL is similarly exposed, attackers could intercept this information, leading to unauthorized access.
- Credential leakage via browser history: If the URLs of the redirection response are stored in the browser's history, an attacker with access to the user's browser history could retrieve these sensitive tokens, leading to unauthorized access.
To defend against this attack:
- Don’t include sensitive data in URLs: Attackers cannot access what is not there. Ensure that authorization requests and responses do not contain sensitive information within the URL path or query parameters.
- Set appropriate HTTP headers: Utilize HTTP headers such as Referrer-Policy to control the information sent in Referer headers, minimizing exposure of sensitive data.
- Use HTTPS: Always use HTTPS to encrypt data transmitted between clients, authorization servers, and resource servers, protecting against interception.
Mix-up attacks
Mix-up attacks happen when an OAuth client, capable of interacting with multiple authorization servers, inadvertently sends an authorization request to the wrong server. If an attacker knows that a client interacts with multiple authorization servers, they might craft a request that appears legitimate to the client but is directed to a different authorization server. If the client doesn't properly validate the server's identity, the attacker could gain unauthorized access to the client's resources or impersonate the client.
For example, imagine a user, Alice, wants to log into a service (Service A) using her credentials from a social media platform (Service B). An attacker could exploit this setup by manipulating Alice into sending her credentials to a fake authorization server that appears similar to Service B's legitimate server. If Alice unknowingly submits her credentials to this malicious server, the attacker gains unauthorized access to her account.
To defend against this attack, use the following strategies:
- Validate the token issuer: Clients should validate the issuer (
iss
) claim in the ID token to ensure that the token was issued by the expected authorization server. This helps prevent accepting tokens from unauthorized or malicious servers. - Use distinct redirect URIs: Clients should use unique redirect URIs for each authorization server they interact with. This practice ensures that authorization responses are sent to the correct client endpoint, preventing attackers from intercepting or misdirecting authorization codes.
Authorization code injection
Authorization code injection is when an attacker inserts a fake authorization code into the response, tricking the client into thinking it's real. This allows the attacker to gain unauthorized access.
This can happen when the client redirects the user to the authorization server, which then redirects back to the client with an authorization code. If the client does not properly validate this code, an attacker who can intercept or manipulate the response might inject a malicious code, leading the client to believe it is valid.
To defend against this attack, follow these best practices:
- Use Proof Key for Code Exchange (PKCE): PKCE enhances the security of the authorization code flow by requiring the client to generate a code verifier and challenge, which are used to validate the authorization code exchange. This mechanism helps prevent authorization code injection attacks. Even though PKCE was initially designed for mobile and public clients (where storing a client secret securely is difficult), it's now considered a best practice to use PKCE for any OAuth flow, including for web apps.
- Use state: The
state
parameter is a random string sent by the client when making the authorization request, and it is returned by the authorization server in the response. The client then checks if the state in the response matches the one it sent. If they match, it means the response is legitimate, and not tampered with. Use the state parameter to maintain state between the request and callback. - Use nonce: The
nonce
parameter is a random value used to prevent replay attacks. It’s included in the authorization request and then returned by the authorization server in the response. The client checks that the nonce in the response matches the one it sent. If they match, it confirms that the response is fresh and not a replay of a previous request. Usenonce
values in authorization requests and validate them in responses to ensure that the authorization code is not reused or injected.
Access token injection
If an attacker gets hold of an access token—by intercepting, stealing, or guessing it—they might try to use it in a client app that accepts the token without checking it properly. If the app does not validate the token's issuer, audience, or scope, the attacker could gain unauthorized access.
To defend against this attack, follow these best practices:
- Validate access tokens: Clients should verify that access tokens are issued by trusted authorization servers and that the tokens' audience (
aud
) and scope align with the client's intended use. For details on all the validations that should be performed, see JWT validation: how-to and best libraries to use. - Store tokens securely: Store access tokens securely on the client side to prevent unauthorized access or leakage. For more on this see JWT storage 101: How to keep your tokens secure.
- Implement token binding: Token binding ensures that the token can only be used by the exact client or device it was issued to. If someone tries to use the token from a different device or client, the server will recognize that the token doesn't match and deny access. This helps prevent attacks where tokens are stolen or intercepted and used maliciously.
- Use short-lived tokens: Use short-lived access tokens and implement refresh token mechanisms to reduce the window of opportunity for an attacker to use a stolen token.
Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery (CSRF) involves an attacker inducing a user to perform actions on a web application where the user is authenticated, without the user's consent.
If a user is logged into a web application, an attacker can craft a request (e.g., submitting a form or clicking a link) that performs an action on the application using the user's credentials. If the application does not properly verify that the request is intentional, it may process the attacker's request as if it were the user's, potentially leading to unauthorized actions.
To defend against this attack, follow these best practices:
- Use anti-CSRF tokens: Implement unique tokens in forms and validate them on the server to ensure that requests are legitimate and originate from the intended user.
- Use the SameSite cookie attribute: Set the
SameSite
attribute on cookies toStrict
orLax
to prevent browsers from sending cookies along with cross-site requests, reducing the risk of CSRF. - Double submit cookies: To verify that requests are legitimate, use two cookies with the same value—one sent as a cookie and the other as a request parameter.
- Custom request headers: Require custom HTTP headers (e.g.,
X-Requested-With
) for state-changing requests, which are difficult for attackers to set in cross-site contexts.
PKCE downgrade attack
A PKCE downgrade attack happens when an authorization server supports PKCE but does not enforce its use for all authorization requests. In such cases, an attacker can manipulate the authorization request to bypass PKCE protections, potentially leading to security vulnerabilities.
To defend against this attack, authorization servers should require PKCE for all authorization requests, ensuring that clients cannot bypass this security mechanism.
Access token leakage at the resource server
Access token leakage happens when tokens are exposed or not handled properly by the resource server, allowing unauthorized users to access protected resources.
An attacker can either set up a fake resource server that mimics a legitimate one, tricking clients into sending access tokens to it, or steal access tokens stored or processed by a compromised resource server.
To defend against this attack:
- Use sender-constrained access tokens: Use access tokens that are bound to the client (sender), ensuring that tokens cannot be used by unauthorized parties. This binding prevents attackers from using stolen tokens.
- Store tokens securely: Resource servers should securely store access tokens and implement strict access controls to prevent unauthorized access. Regular audits and monitoring can help detect and respond to suspicious activities.
Misuse of stolen access tokens
Access tokens, if intercepted or leaked, can be used by attackers to make unauthorized requests to resource servers. This misuse can lead to data breaches, unauthorized data manipulation, or other malicious activities.
To defend against this:
- Store and transmit tokens securely: The first line of defense is not having your tokens stolen in the first place. Store access tokens securely using appropriate storage mechanisms and ensure they are transmitted over HTTPS to prevent interception.
- Implement token expiry and rotation: Be prepared to minimize damage if a token does get stolen. This can be achieved by setting short lifespans for access tokens and rotating them periodically. This limits the window of opportunity for attackers to misuse stolen tokens.
- Monitor and revoke tokens: A final line of defense is to monitor the usage of access tokens and have the capability to revoke tokens if you suspect they might be compromised.
Open redirection
Open redirection happens when a client or authorization server redirects a user to a potentially malicious external URL, leading to phishing attacks or exposure of sensitive information. Attackers can create fake URLs that, when clicked, send users through a trusted site to a harmful one. This can trick users into giving away sensitive information or doing something they didn’t mean to.
To defend against this:
- Validate redirect URIs: Clients and authorization servers should maintain a whitelist of allowed redirect URIs. Before performing any redirection, they must verify that the target URI is on this list to prevent unauthorized redirects.
- Authenticate request origins: Ensure that the origin and integrity of incoming requests can be authenticated. This involves verifying that requests come from legitimate sources and have not been tampered with, reducing the risk of malicious redirection.
307 redirect
In OAuth 2.0 flows, especially during the authorization code exchange, an authorization server might issue a 307
redirect to the client's redirect URI. If the authorization server does not validate that this URI matches the pre-registered redirect URI associated with the client, an attacker could manipulate the redirect URI to an unauthorized location. This can lead to leaking sensitive information, such as authorization codes or access tokens, to malicious actors.
To defend against this:
- Validate redirect URIs: Authorization servers should rigorously validate that the redirect URI in the authorization request matches one of the pre-registered URIs associated with the client. This ensures that responses, including
307
redirects, are sent to trusted locations. - Avoid unnecessary redirects: Minimize the use of
307
redirects in the authorization flow. If redirects are necessary, ensure they serve a clear purpose and do not inadvertently expose sensitive data.
TLS terminating reverse proxies
This attack is about the use of reverse proxies that handle TLS (Transport Layer Security) connections on behalf of backend application servers.
In typical deployments, a reverse proxy sits between clients and application servers, handling tasks like load balancing and SSL/TLS termination. When a reverse proxy terminates TLS connections, it decrypts incoming encrypted traffic before forwarding it to backend servers. If the proxy does not securely pass on information about the original client's IP address and the protocol used (e.g., HTTPS), backend servers might misinterpret the nature of the request. This misinterpretation can lead to inaccurate access control decisions or exposure of sensitive data.
To defend against this:
- Use the X-Forwarded-Proto header: Configure the reverse proxy to include the
X-Forwarded-Proto
HTTP header in requests forwarded to backend servers. This header indicates the original protocol (e.g.,HTTPS
) used by the client, allowing the backend to apply appropriate security measures. - Use the X-Forwarded-For header: Ensure that the proxy forwards the
X-Forwarded-For
header, which contains the client's original IP address. This practice enables the backend server to make accurate access control decisions based on the client's identity. - Trust proxy headers: Set up backend servers to trust and process the
X-Forwarded-Proto
andX-Forwarded-For
headers correctly. This configuration ensures that the servers can reconstruct the client's original request context, including the protocol and IP address. - Implement security checks based on forwarded data: Utilize the information from these headers to enforce security policies, such as redirecting HTTP requests to HTTPS or restricting access based on client IP addresses.
Refresh token protection
Refresh tokens are special tokens used to get a new access token when the old one expires. Instead of asking the user to log in again, the app can use the refresh token to request a new access token, allowing the user to stay logged in without interruption.
If refresh tokens are not properly protected, attackers who gain access to them can obtain new access tokens, potentially leading to unauthorized access to protected resources.
To defend against this:
- Secure storage: Store refresh tokens securely on the client side, using mechanisms that prevent unauthorized access.
- Secure transmission: Transmit refresh tokens over secure channels (e.g., HTTPS) to prevent interception during transmission.
- Limit scope and lifetime: Assign minimal scope and lifetime to refresh tokens to reduce the impact of potential compromise.
- Use refresh token rotation: Implement refresh token rotation, where each use of a refresh token to obtain a new access token also issues a new refresh token, invalidating the old one. This limits the window of opportunity for attackers if a token is compromised.
Client impersonating resource owner
In this attack, the attacker registers a client application with the authorization server, possibly using misleading information to appear trustworthy. They then trick users into authorizing this malicious client, granting it access to their resources. With the obtained access tokens, the attacker can access or manipulate the user's protected data without authorization.
To defend against this attack:
- Verify client identity: Authorization servers should implement thorough verification of client identities during the registration process. This includes validating the client's legitimacy and ensuring that provided information is accurate and trustworthy.
- Use secure client credentials: Assign unique and secure credentials to each client, such as client secrets or certificates, to authenticate clients during the authorization process.
- Inform users about authorized clients: Educate users on how to recognize legitimate client applications and encourage them to review and understand the permissions they grant.
- Provide clear consent prompts: Ensure that consent screens presented to users clearly identify the requesting client and specify the scope of access being requested.
- Implement logging and monitoring: Authorization servers should log client activities and monitor for unusual or unauthorized access patterns.
- Conduct regular audits: Perform periodic audits of client applications and their access logs to detect and respond to potential impersonation attempts.
Clickjacking
Clickjacking is a type of attack where a malicious website tricks a user into clicking on something different from what they think they're clicking on. This is done by hiding a clickable element (like a button or link) inside an invisible frame or overlay on a page.
For example, the user might think they are clicking a harmless button, but in reality, they're clicking on something malicious, like confirming a transaction or changing account settings. This can lead to unintended actions being taken without the user’s knowledge.
To defend against this:
- Use X-Frame-Options: The X-Frame-Options HTTP header controls whether your web pages can be embedded in frames or iframes. The available options are:
DENY
: Completely disallow embedding of the page in any frame.
SAMEORIGIN
: Allow embedding only if the frame is from the same origin as the page.
ALLOW-FROM uri
: Allow embedding only from the specified origin. Note that support for this option may vary across browsers.
- Use Content Security Policy (CSP): Use the
frame-ancestors
directive in your Content Security Policy (CSP) to specify which sources are allowed to embed your content in a frame, iframe, or similar HTML elements. For example, inContent-Security-Policy: frame-ancestors 'self' example.com;
the 'self' only allows your own site to embed content (i.e., no other sites can embed your content), while theexample.com
allowsexample.com
to embed your content in a frame. - Design user interfaces with clickjacking resistance: Design sensitive actions to occur outside of frames or iframes and implement scripts that prevent your site from being embedded in frames.
Attacks on In-Browser Communication Flows
By manipulating the parameters of authorization requests or responses, attackers can redirect authorization codes or tokens to malicious endpoints under their control. This allows them to intercept sensitive data or impersonate users.
For example, if the user's connection to the client is compromised (e.g., through a man-in-the-middle attack), the attacker can modify the authorization request. This manipulation can lead to the authorization code or access token being sent to a malicious endpoint controlled by the attacker, rather than the legitimate client application. Consequently, the attacker gains unauthorized access to the user's resources
To defend against this:
- Restrict receiver origins: Authorization servers should restrict the origins that can receive authorization responses to a predefined list of trusted endpoints. This prevents unauthorized parties from intercepting sensitive data.
- Validate redirect URIs: Implement strict validation of redirect URIs by ensuring that they match pre-registered URIs exactly, except for port numbers in localhost redirection URIs for native apps. This measure helps prevent unauthorized redirections and potential leakage of authorization codes and access tokens.
- Validate the sender origin: Perform thorough validation of the sender's origin (e.g., the domain of the authorization server) before processing authorization responses. This helps prevent injection attacks that could lead to unauthorized access or data leakage.
Other best practices and recommendations
Besides the attacks described in this article, RFC 9700 includes some additional best practices for optimal security:
- Avoid using the implicit grant: The implicit grant type is vulnerable to certain attacks, especially the risk of access tokens being exposed in URLs (such as in browser history or server logs). Use PKCE instead, as it is more secure and avoids exposing tokens in URLs.
- Avoid using the resource owner password credentials grant: In the ROPC flow, the user has to provide their username and password directly to the application, which means that the application (and anyone who gains access to it) can potentially access sensitive credentials. This is a major security risk and you should avoid using it. Use instead more secure OAuth flows, like the authorization code flow, where user consent is required, or the client credentials flow for machine-to-machine communication.
- Use strong client authentication: Use client secrets, public/private key pairs, or client certificates to authenticate clients to the authorization server. For public clients (such as mobile apps), client authentication methods like PKCE are recommended.
- Use strong user authentication: Weak user authentication can be a point of entry for attackers, especially when combined with poor authorization practices. Implement strong multi-factor authentication (MFA) mechanisms for users to ensure that even if an attacker steals credentials, they will still require additional authentication factors.
- Educate and train developers: Developers need to be aware of OAuth 2.0 best practices to avoid common mistakes. Provide ongoing security training for developers, ensuring they understand OAuth 2.0 vulnerabilities and how to implement the protocol securely.
- Use secure libraries and frameworks: Always use well-reviewed libraries and frameworks that implement OAuth 2.0 securely and follow best practices. Avoid creating custom implementations.
If you are looking for an authorization server that implements best practices and will keep your app and your users secure, check out WorkOS. With SDKs in every popular language, the option to use your own login box or the fully customizable AuthKit, support for every protocol and all major identity providers, and a flat rate for each company you onboard — whether they bring 10 or 10,000 SSO users to your app — we’ve got authentication and authorization covered so you can go ahead and focus on building your product.