Token Management
When to Use This Skill
Use this skill when:
- Building a .NET worker service or daemon that calls APIs using the client credentials flow
- Building an ASP.NET Core web application that calls APIs on behalf of the currently logged-in user
- Integrating
Duende.AccessTokenManagementorDuende.AccessTokenManagement.OpenIdConnectwithIHttpClientFactory - Configuring token caching — in-memory, distributed (Redis), or hybrid — for machine-to-machine tokens
- Adding DPoP (Demonstrating Proof-of-Possession) key binding to access tokens
- Implementing API-to-API delegation where a downstream service calls further APIs with either user tokens or client credentials
- Revoking refresh tokens on user sign-out
Core Principles
- Prefer Automatic Over Manual — Use
IHttpClientFactory-integrated clients; they acquire, cache, refresh, and attach tokens transparently. CallGetAccessTokenAsyncmanually only when the factory pattern is insufficient. - Never Cache Tokens in Code — The library owns the cache. Do not store tokens in instance fields, static variables, or application-managed caches. Call the service on every request and let it serve from cache.
SaveTokens = trueIs Required for User Tokens — The OIDC handler must persist tokens into the authentication session. This is the most common misconfiguration.- Refresh Tokens Must Be Revoked at Sign-Out — Call
e.HttpContext.RevokeRefreshTokenAsync()inOnSigningOutto revoke the refresh token at the authorization server, preventing reuse after logout. - v4 Uses
HybridCache; v3 UsesIDistributedCache— The caching layer changed between major versions. v4'sHybridCacheis two-tier and automatic; v3 requires an explicitAddDistributedMemoryCache()or Redis registration. - Resiliency Is Included in
AddClientCredentialsHttpClient— This registration adds a once-retry handler for401 Unauthorizedresponses (handles token expiry and DPoP nonce challenges). When usingAddClientCredentialsTokenHandlerdirectly, add it explicitly.
Related Skills
aspnetcore-authentication— cookie and OIDC handler setup required for user token managementidentityserver-configuration— configuring the authorization server that issues tokensoauth-oidc-protocols— protocol fundamentals underlying client credentials and refresh token flowsduende-bff— BFF pattern integrates this library automatically for proxied API calls
Docs: https://docs.duendesoftware.com/accesstokenmanagement/
Pattern 1: Machine-to-Machine (Client Credentials) — Worker Services
Package
Registration
Available client options:
TokenEndpoint— URL of the OAuth token endpointClientId/ClientSecret— client credentialsClientCredentialStyle—AuthorizationHeader(default) orPostBodyScope— requested scope (optional; overridable per request)Resource— resource indicator per RFC 8707 (optional)HttpClientName— custom backchannel HTTP client name from the factoryDPoPJsonWebKey— JWK for DPoP-bound tokens (see Pattern 5)
Automatic via HttpClientFactory (Recommended)
Usage — no token code required at the call site:
Resiliency handler —
AddClientCredentialsHttpClientautomatically adds a resiliency handler that retries once on401 Unauthorized. This covers token expiry and DPoP nonce challenges. When usingAddClientCredentialsTokenHandlerdirectly, add it explicitly:
Manual Token Retrieval (Advanced)
In v3, the service was
IClientCredentialsTokenManagementServiceand the result was read via.Value. In v4 it isIClientCredentialsTokenManagerand the result isTokenResult<ClientCredentialsToken>— use.Succeeded/.GetToken().
Pattern 2: User Token Management — Web Applications
Package
Registration
SaveTokens = trueis mandatory. Without it, the library cannot read or refresh the user's access token. This is the most common misconfiguration causingInvalidOperationExceptionat runtime.
Automatic via HttpClientFactory (Recommended)
Usage in a controller:
Manual Token Retrieval (Advanced)
HttpContext extension methods are also available:
gRPC Support
Use AddUserAccessTokenHandler and AddClientAccessTokenHandler when registering typed gRPC clients:
Pattern 3: Token Caching
v4 — HybridCache (Default)
In v4, client credentials tokens are cached using HybridCache (ASP.NET Core 9+). It is two-tier: in-memory L1 + optional remote L2. No explicit registration is required for the default in-memory tier.
Global cache options:
Default cache key format:
scope and resource values are MD5-hashed to keep key length bounded. Implement IClientCredentialsCacheKeyGenerator to supply custom keys when adding custom TokenRequestParameters.
v3 — IDistributedCache
Encrypting Cached Tokens (v4)
When sharing a remote cache with other applications, encrypt tokens at rest using a custom IHybridCacheSerializer<ClientCredentialsToken>:
Scoping a Custom Cache to This Library Only
Pattern 4: Configuration Options
ClientCredentialsTokenManagementOptions
UserTokenManagementOptions
Per-Request Parameter Overrides
For IHttpClientFactory clients, parameters are wired at registration time:
Pattern 5: DPoP (Demonstrating Proof-of-Possession)
DPoP binds an access token to a client-held asymmetric key, preventing replay attacks even if the token is stolen. Configure it by setting DPoPJsonWebKey on the client credentials client or UserTokenManagementOptions, and optionally implement IDPoPKeyStore for runtime key rotation.
Full details in sub-document — See
docs/dpop.md[blocked] for JWK generation, per-client configuration,IDPoPKeyStore, session size implications, and the key-persistence pitfall.
Pattern 6: API-to-API Token Delegation
An API can either forward the user's access token to a downstream API (when the downstream accepts the same audience) or use a dedicated client credentials token (when the downstream requires a service identity).
Full details in sub-document — See
docs/api-delegation.md[blocked] for both approaches with complete code examples and a decision guide.
Pattern 7: Dynamic Client Configuration
Use IConfigureNamedOptions<ClientCredentialsClient> when token endpoint configuration must be resolved at runtime — for example, from OIDC discovery:
Pattern 8: Custom Token Storage
User Tokens — Replace the Default Cookie Session Store
By default, user access and refresh tokens are stored inside the ASP.NET Core authentication cookie. Replace IUserTokenStore when this is insufficient — for example, when using server-side sessions:
Client Credentials — Replace the Cache Implementation
Pattern 9: Blazor Server Token Management
Blazor Server circuits outlive the initial HTTP request. Once a circuit is established, HttpContext is null, making the default cookie-based IUserTokenStore unusable. Use AddBlazorServerAccessTokenManagement<T>() with a persistent IUserTokenStore (e.g., database-backed), and capture tokens in OnTokenValidated during the initial OIDC flow.
Full details in sub-document — See
docs/blazor-server.md[blocked] for the fullIUserTokenStoreimplementation,OnTokenValidatedsetup, and theHttpContext-null pitfall.
Pattern 10: Client Assertions (private_key_jwt)
Use IClientAssertionService to authenticate with signed JWTs instead of shared client secrets. CRITICAL: set the JWT Audience to the authorization server's issuer URL — NOT the token endpoint URL. Using the token endpoint URL was the root cause of CVE-2025-27370 and CVE-2025-27371.
Full details in sub-document — See
docs/client-assertions.md[blocked] for the fullIClientAssertionServiceimplementation, correct audience configuration, and CVE context.
Pattern 11: Custom Token Request Customization
Use ITokenRequestCustomizer (v4) to dynamically modify token request parameters per outgoing HTTP request — useful for multi-tenant scenarios where different tenants require different API resources or scopes. Use the with expression to create a modified copy of baseParameters; do not mutate it.
Full details in sub-document — See
docs/customization.md[blocked] for the fullITokenRequestCustomizerimplementation and registration pattern.
Pattern 12: Custom Token Retrieval
Implement AccessTokenRequestHandler.ITokenRetriever to completely replace the default token retrieval logic with custom selection or caching behavior.
Full details in sub-document — See
docs/customization.md[blocked] for the fullITokenRetrieverimplementation andAddHttpMessageHandlerregistration pattern.
Sub-Documents
Load these sub-documents when the user's question specifically targets one of these areas:
Complete Example: Web App with User and Client Credentials
Common Pitfalls
1. Missing SaveTokens = true for User Tokens
2. Missing offline_access Scope
3. Not Revoking Refresh Tokens at Sign-Out
4. Caching Tokens Manually Alongside the Library
5. Calling .GetToken() Without Checking Succeeded
6. Using AddClientCredentialsTokenHandler Without Resiliency
7. v3 — Forgetting to Register a Distributed Cache
8. Regenerating DPoP Keys on Every Process Restart
9. Setting Client Assertion Audience to the Token Endpoint URL
CVE-2025-27370 and CVE-2025-27371 were caused by this exact mistake. Authorization servers that accept both values allow token endpoint confusion attacks.
10. Using HttpContext to Access Tokens in Blazor Server Components
11. Setting CacheLifetimeBuffer to 0
Version Reference: v3 → v4
Resources
- Access Token Management Overview
- Service Workers / Background Tasks
- Web Applications (User Tokens)
- Blazor Server
- Advanced: Client Credentials Options
- Advanced: User Token Options
- Advanced: Client Assertions
- Advanced: DPoP
- Advanced: Extensibility
- v3 → v4 Upgrade Guide
- NuGet: Duende.AccessTokenManagement
- NuGet: Duende.AccessTokenManagement.OpenIdConnect
- GitHub: DuendeSoftware/foss (access-token-management)


