CVE-2025-26620 is a newly disclosed vulnerability affecting the Duende.AccessTokenManagement library for .NET, which is widely used for managing OAuth and OpenID Connect access tokens. This post offers a technical deep dive into the vulnerability, its exploitation, affected code paths, and actionable guidance for developers.

What is Duende.AccessTokenManagement?

Duende.AccessTokenManagement provides a set of libraries for easy, robust handling of OAuth and OpenID Connect tokens in .NET applications. It streamlines obtaining, caching, and refreshing access tokens for both user and client credential scenarios.

What’s the Issue?

A race condition in token retrieval using the client credentials flow allows concurrent requests—with different protocol parameters—to receive the same cached access token. This means that a request intended to get a token with a specific scope or resource may end up using a token generated under *different* parameters if multiple requests happen at the same moment.

This most seriously impacts users who programmatically supply different TokenRequestParameters on a per-request basis using advanced scenarios.

Most basic users🌱—who set up the client credentials flow once and don’t customize each request—are NOT affected.

Sample Problematic Usage

// Two requests made concurrently, with different scopes
var token1Task = HttpContext.GetClientAccessTokenAsync(new TokenRequestParameters { Scope = "api.read" });
var token2Task = HttpContext.GetClientAccessTokenAsync(new TokenRequestParameters { Scope = "api.write" });

var token1 = await token1Task;
var token2 = await token2Task;

// Both token1 and token2 may *unexpectedly* be the same!
Console.WriteLine(token1 == token2); // True -- This is the vulnerability!

Root Cause

The underlying token cache key did not include the protocol parameters (like scope or resource), so the cache could not distinguish between concurrent requests for different tokens. The first completed token request wins, and its token is returned to all concurrent callers. This presents two possible impacts:

Exploit Scenario

1. An application makes concurrent calls for access tokens for the same client, but with different requested scopes or resources—both at the same time.

Due to the bug, both calls receive the *same* access token, even though the requests differ.

3. The application might then send a token lacking the intended privileges, or accidentally with too broad a privilege, to a resource server.

Here’s a real-world style exploit illustration

public async Task ExploitRaceConditionAsync(HttpContext context)
{
    // Request tokens for two different resource servers at the same time
    var params1 = new TokenRequestParameters { Resource = "api-1", Scope = "read:api-1" };
    var params2 = new TokenRequestParameters { Resource = "api-2", Scope = "read:api-2" };

    var task1 = context.GetClientAccessTokenAsync(params1);
    var task2 = context.GetClientAccessTokenAsync(params2);

    var token1 = await task1;
    var token2 = await task2;

    // Both requests could receive the *same* token, even though they’re for different APIs
    Console.WriteLine("Token1: " + token1);
    Console.WriteLine("Token2: " + token2);
    Console.WriteLine("Are tokens equal? " + (token1 == token2));
}

Impact: If your authorization system grants/restricts access based on these tokens, your application could send unintended tokens to resource servers, violating least privilege or failing requests.

NOT at risk: apps that use a single, static client credentials configuration (the common case)

- POTENTIALLY at risk: apps that call the affected methods with varying protocol parameters concurrently in advanced, multi-tenant, or dynamic-scope scenarios

How to Fix

Solution: Upgrade to the latest release of Duende.AccessTokenManagement as soon as possible.

- The maintainers updated the cache key logic to correctly distinguish requests by their specific protocol parameters, ending the race condition.

Custom Token Cache Implementers

If you’ve customized IClientCredentialsTokenCache by subclassing the default DistributedClientCredentialsTokenCache, you’ll need to update your constructor to accept a new dependency:

public class MyCustomTokenCache : DistributedClientCredentialsTokenCache
{
    public MyCustomTokenCache(
        // add the new parameter:
        ITokenRequestSynchronization tokenRequestSync, 
        // ...other dependencies
    ) : base(tokenRequestSync, /* ...other dependencies */)
    {
    }
}

- *More details:* Duende Access Token Management Changelog

Mitigation and Recommendations

- Update immediately if your usage matches the risky pattern (dynamic parameters & concurrent requests).

For custom token caches, adapt your constructors as shown above.

- Audit your codebase: Search for usages of GetClientAccessTokenAsync(TokenRequestParameters) and review for concurrent use cases.

References and Further Reading

- Official Duende Advisory *(placeholder for CVE advisory link)*
- NuGet Package
- Changelog
- OAuth 2. Client Credentials Grant

Summary

Most developers won’t be affected by CVE-2025-26620 unless using highly dynamic token request scenarios. But for advanced, multi-tenant, or high-security .NET applications, this race condition could mean your app is misusing or leaking sensitive token privileges.

Stay safe—keep dependencies up-to-date and always audit for concurrency bugs around authentication!


*This summary was written exclusively for a technical audience, using clear, actionable language to help teams understand and remediate this subtle yet important vulnerability.*

Timeline

Published on: 02/18/2025 18:15:36 UTC