October 11, 2026
$2,500 Bug Bounty Lesson in OAuth: How Race Conditions Can Break Token Revocation
One-time authorization codes, refresh tokens, and a concurrency bug that can leave revoked access alive

By Monika
6 min read
By Monika Sharma
What if a user clicks "Revoke Access", expects an application to lose access to their account, but an old token continues working?
That is the security problem explored in HackerOne report (https://hackerone.com/reports/55140), titled Race Conditions in OAuth 2 API Implementations.
Submitted by researcher dor1s to the Internet Bug Bounty program on April 6, 2015, the report describes a subtle flaw in OAuth implementations: concurrent requests may process the same authorization code or refresh token in a way that creates multiple valid token pairs.
The result can be much more serious than a duplicate token. If revocation does not invalidate every token associated with the authorization grant, a user may lose the ability to reliably disconnect an application.
This is a useful case study for bug bounty hunters because it demonstrates how a small timing issue can undermine an important security control.
1. Understanding the OAuth Token Flow
OAuth 2.0 allows a user to authorize an application to access specific resources without sharing their password with that application.
A simplified authorization-code flow looks like this:
- The user authorizes an application.
- The application receives an authorization code through its registered redirect URI.
- The application exchanges that code for an access token.
- Depending on the implementation and grant configuration, the application may also receive a refresh token.
- The access token is used to call protected APIs, while a refresh token can be used to obtain new access tokens.
These credentials serve different purposes.
- Authorization code: A temporary credential intended to be redeemed once.
- Access token: A credential used to access protected resources.
- Refresh token: A credential used to obtain replacement access tokens when permitted.
The security of this flow depends on more than issuing the right credentials. The authorization server must also handle simultaneous requests and revocation correctly.
2. What Is an OAuth Race Condition?
A race condition occurs when the security outcome of an operation depends on the timing or order of concurrent actions.
Imagine two requests attempting to redeem the same authorization code at nearly the same moment.
A vulnerable implementation might process the requests like this:
- Request A checks whether the authorization code has already been used.
- Request B checks the same code before Request A records its redemption.
- Both requests pass their checks.
- Both requests proceed to issue credentials.
This is a simplified illustration, not a claim that every OAuth implementation follows this exact internal sequence.
The underlying problem is a failure to make the validation and consumption of a one-time credential atomic.
The key lesson: Checking a credential and marking it as used must behave like one indivisible operation, even when multiple requests arrive simultaneously.
3. Why Multiple Valid Tokens Can Become a Security Vulnerability
Generating more than one token is not automatically enough to establish a serious security issue. The real question is whether those tokens create an authorization state the system was not supposed to allow.
Consider a hypothetical application that receives two valid access-token and refresh-token pairs for a single authorization event.
If the user later revokes the application's access, the authorization server should enforce that revocation across the relevant token family or grant.
But imagine this outcome:
- Token pair A is invalidated.
- Token pair B remains valid.
- The application can still access protected resources using token B.
The user believes access has been revoked, yet an independently valid credential continues to work.
That is where the race condition can become an authorization and access-revocation failure.
The original report described testing 11 OAuth API implementations. The researcher reported six vulnerable targets and discussed additional implementations that exhibited race conditions but had mitigating behavior.
This distinction matters: a race condition is the underlying weakness, while the ability to retain unauthorized access determines the practical impact.
4. Why Refresh-Token Rotation Matters
Refresh tokens deserve particular attention because they can provide a continuing path to obtain new access tokens.
A well-designed refresh-token rotation mechanism generally replaces the old refresh token when it is used. The authorization server should ensure that concurrent redemption cannot create multiple independent, valid token lineages unintentionally.
Suppose two requests attempt to redeem the same refresh token.
If both requests succeed independently, the server may issue replacement credentials for both requests. Depending on the implementation, this could undermine the intended single-use behavior of refresh-token rotation.
A stronger implementation can detect reuse and apply an appropriate response, such as revoking the affected token family when the applicable security policy calls for it.
The exact behavior depends on the authorization server's design. Not every OAuth deployment uses refresh tokens, and not every rotation policy responds to reuse in the same way.
For bug bounty hunters, the important questions are:
- Is refresh-token rotation enforced correctly?
- Can the same refresh token be redeemed concurrently?
- Are replacement tokens independently usable?
- Does revocation invalidate all credentials associated with the affected grant?
- Can the original authorization be restored or continued after revocation?
These questions help distinguish a timing anomaly from an exploitable security vulnerability.
5. How to Investigate This Bug Class in an Authorized Test Environment
If you are assessing an application under an authorized bug bounty program, start by understanding the intended behavior before testing concurrency.
Step 1: Read the OAuth implementation and documentation
Identify the supported grant types, token-lifetime rules, refresh-token rotation behavior, and revocation policy.
Check the program's scope and testing restrictions before sending requests.
Step 2: Establish a normal baseline
Use an account and OAuth client you control. Observe the normal authorization-code exchange and, if supported, the refresh-token lifecycle.
Record which credentials are issued and how the server responds when a credential has already been consumed.
Step 3: Examine concurrency safety
Review the relevant server-side logic, if source code is available, to determine whether credential validation and consumption are performed atomically.
Pay attention to database transactions, unique constraints, locking, and concurrent updates.
If source code is unavailable, use only the controlled test accounts and request limits permitted by the program. Avoid high-volume testing that could affect other users or degrade service availability.
Step 4: Verify the actual security impact
A duplicate response or an unexpected token alone does not establish a meaningful vulnerability.
The important evidence is whether multiple credentials remain valid when the authorization server's policy says they should not, and whether the behavior defeats an expected revocation or rotation guarantee.
Use a harmless, authorized API endpoint to verify token validity.
Step 5: Test revocation behavior
Using only credentials and accounts you control, determine whether revocation invalidates the associated authorization as intended.
Document any surviving credential, the specific policy it violates, and the security consequence.
Stop once you have enough evidence to demonstrate the issue. Do not test against another person's account or attempt to maintain unauthorized access.
6. How Developers Can Prevent OAuth Race Conditions
The most effective fixes address both concurrency and authorization state.
Make one-time credential redemption atomic. The server should validate and consume an authorization code as a single atomic operation. Concurrent requests must not both successfully redeem the same code.
Implement refresh-token rotation safely. The server should coordinate concurrent refresh attempts and enforce the intended single-use policy. Reuse detection should follow a clearly defined security policy.
Associate tokens with an authorization grant. The server should maintain enough state to revoke the credentials belonging to the same authorization, rather than treating every token as an unrelated object.
Enforce revocation consistently. Revocation must be reflected by the systems that validate access tokens. With self-contained tokens, this may require short lifetimes, introspection, denylisting, or another appropriate mechanism.
Add concurrency tests. Security tests should cover simultaneous redemption, replay of consumed credentials, refresh-token reuse, and revocation while credentials are active.
Use database guarantees where appropriate. Transactions, conditional updates, and uniqueness constraints can prevent multiple requests from consuming the same one-time credential.
The implementation details will vary, but the goal is the same: the server must preserve its authorization guarantees even when requests arrive at almost exactly the same time.
7. What Bug Bounty Hunters Can Learn From This Report
This case study offers several lessons for security researchers.
First, look beyond individual HTTP responses. A response that appears successful or unsuccessful may not tell the whole story. Security depends on the resulting authorization state.
Second, investigate state-changing operations. Token issuance, token rotation, password resets, invitation acceptance, coupon redemption, and account recovery can all depend on operations being performed only once.
Third, understand the difference between a race condition and its impact. Duplicate output is not automatically a bounty-worthy vulnerability. Demonstrating a violation of a security guarantee makes the report stronger.
Fourth, focus on reproducible evidence. A clear timeline, expected behavior, observed behavior, and practical impact help a triager understand the problem.
Finally, read historical reports critically. The OAuth ecosystem has evolved since 2015. Modern implementations may include stronger rotation, reuse detection, and revocation mechanisms, so the old report should be treated as a learning case study rather than proof that a current service is vulnerable.
Conclusion
OAuth race conditions show why secure authentication is not just about issuing tokens. It is also about controlling when credentials can be used, how they are replaced, and what happens when access is revoked.
The central lesson from HackerOne report 55140 is simple: a one-time security operation must remain one-time under concurrency, and revocation must enforce the user's intended loss of access.
For bug bounty hunters, this is a reminder to investigate application state, concurrency, and security guarantees instead of focusing only on individual requests.
For developers, it is a reminder that authorization logic must remain correct even when multiple requests arrive at the same moment.
Thank you ✨