Access Token Management: Documentation for Duende's open-source Access Token Management library which provides automatic access token management features for .NET worker and ASP.NET Core web applications
-----
# Access Token Management
> The Duende.AccessTokenManagement library provides automatic access token management features for .NET applications
The `Duende.AccessTokenManagement` library provides automatic access token management features for .NET worker and ASP.NET Core web applications: * Automatic acquisition and lifetime management of client credentials based access tokens for machine-to-machine communication (using the `Duende.AccessTokenManagement` package) * Automatic access token lifetime management using a refresh token for API calls on behalf of the currently logged-in user (using the `Duende.AccessTokenManagement.OpenIdConnect` package) * Revocation of access tokens ## Machine-To-Machine Token Management [Section titled “Machine-To-Machine Token Management”](#machine-to-machine-token-management) To get started, install the NuGet Package: ```bash dotnet add package Duende.AccessTokenManagement ``` See [Service Workers and Background Tasks](/accesstokenmanagement/workers/) for more information on how to get started. [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/access-token-management)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.AccessTokenManagement/)View the package on NuGet.org. ## User Token Management [Section titled “User Token Management”](#user-token-management) To get started, install the NuGet Package: ```bash dotnet add package Duende.AccessTokenManagement.OpenIdConnect ``` See [Web Applications](/accesstokenmanagement/web-apps/) for more information on how to get started. [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/access-token-management)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.AccessTokenManagement.OpenIdConnect/)View the package on NuGet.org. ## License And Feedback [Section titled “License And Feedback”](#license-and-feedback) **Duende.AccessTokenManagement** is released as open source under the Apache 2.0 license. Bug reports, feature requests and contributions are welcome via the [Duende community discussions](https://duende.link/community).
-----
# Client Assertions
> Learn how to use client assertions instead of shared secrets for token client authentication in Duende.AccessTokenManagement.
If your token client is using a client assertion instead of a shared secret, you can provide the assertion in two ways: * Use the request parameter mechanism to pass a client assertion to the management * Implement the `IClientAssertionService` interface to centralize client assertion creation Here’s a sample client assertion service using the Microsoft JWT library: * V4 ClientAssertionService.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; public class ClientAssertionService(IOptionsSnapshot options) : IClientAssertionService { public Task GetClientAssertionAsync( ClientCredentialsClientName? clientName = null, TokenRequestParameters? parameters = null) { if (clientName == "invoice") { var options1 = options.Get(clientName); var descriptor = new SecurityTokenDescriptor { Issuer = options1.ClientId!.ToString(), // Set the audience to the url of identity server. Do not use the tokenurl to build the autority. Audience = "https://--url-to-authority-here--", Expires = DateTime.UtcNow.AddMinutes(1), SigningCredentials = GetSigningCredential(), Claims = new Dictionary { { JwtClaimTypes.JwtId, Guid.NewGuid().ToString() }, { JwtClaimTypes.Subject, options.ClientId.ToString()! }, { JwtClaimTypes.IssuedAt, DateTimeOffset.UtcNow.ToUnixTimeSeconds() } }, AdditionalHeaderClaims = new Dictionary { { JwtClaimTypes.TokenType, "client-authentication+jwt" } } }; var handler = new JsonWebTokenHandler(); var jwt = handler.CreateToken(descriptor); return Task.FromResult(new ClientAssertion { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = jwt }); } return Task.FromResult(null); } private SigningCredentials GetSigningCredential() { throw new NotImplementedException(); } } ``` * V3 ClientAssertionService.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; public class ClientAssertionService(IOptionsSnapshot options) : IClientAssertionService { public Task GetClientAssertionAsync( string? clientName = null, TokenRequestParameters? parameters = null) { if (clientName == "invoice") { var options1 = options.Get(clientName); var descriptor = new SecurityTokenDescriptor { Issuer = options1.ClientId, // Set the audience to the url of identity server. Do not use the tokenurl to build the autority. Audience = "https://--url-to-authority-here--", Expires = DateTime.UtcNow.AddMinutes(1), SigningCredentials = GetSigningCredential(), Claims = new Dictionary { { JwtClaimTypes.JwtId, Guid.NewGuid().ToString() }, { JwtClaimTypes.Subject, options1.ClientId! }, { JwtClaimTypes.IssuedAt, DateTime.UtcNow.ToEpochTime() } }, AdditionalHeaderClaims = new Dictionary { { JwtClaimTypes.TokenType, "client-authentication+jwt" } } }; var handler = new JsonWebTokenHandler(); var jwt = handler.CreateToken(descriptor); return Task.FromResult(new ClientAssertion { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = jwt }); } return Task.FromResult(null); } private SigningCredentials GetSigningCredential() { throw new NotImplementedException(); } } ``` For a complete working example, see the [WebClientAssertions sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/WebClientAssertions).
-----
# Customizing Client Credentials Token Management
> Learn how to customize client credentials token management including client options, backchannel communication, and token caching configurations.
The most common way to use the access token management for [machine-to-machine communication](/accesstokenmanagement/workers/). However, you may want to customize certain aspects of it. ## Client Options [Section titled “Client Options”](#client-options) You can add token client definitions to your host while configuring the DotNet service provider, e.g.: * V4 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("invoices", client => { client.TokenEndpoint = new Uri("https://sts.company.com/connect/token"); client.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); client.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); client.ClientCredentialStyle = ClientCredentialStyle.AuthorizationHeader; client.Scope = Scope.Parse("list"); client.Resource = Resource.Parse("urn:invoices"); }); ``` * V3 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("invoices", client => { client.TokenEndpoint = "https://sts.company.com/connect/token"; client.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; client.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; client.ClientCredentialStyle = ClientCredentialStyle.AuthorizationHeader; client.Scope = "list"; client.Resource = "urn:invoices"; }); ``` You can set the following options: * `TokenEndpoint` - URL of the OAuth token endpoint where this token client requests tokens from * `ClientId` - client ID * `ClientSecret` - client secret (if a shared secret is used) * `ClientCredentialStyle` - Specifies how the client ID / secret is sent to the token endpoint. Options are using the authorization header, or POST body values (defaults to header) * `Scope` - the requested scope of access (if any) * `Resource` - the resource indicator (if any) Internally the standard .NET options system is used to register the configuration. This means you can also register clients like this: * V4 Program.cs ```csharp services.Configure("invoices", client => { client.TokenEndpoint = new Uri("https://sts.company.com/connect/token"); client.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); client.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); client.Scope = Scope.Parse("list"); client.Resource = Resource.Parse("urn:invoices"); }); ``` * V3 Program.cs ```csharp services.Configure("invoices", client => { client.TokenEndpoint = "https://sts.company.com/connect/token"; client.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; client.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; client.Scope = "list"; client.Resource = "urn:invoices"; }); ``` Or use the `IConfigureNamedOptions` if you need access to the ASP.NET Core service provider during registration, e.g.: * V4 ClientCredentialsClientConfigureOptions.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; public class ClientCredentialsClientConfigureOptions(DiscoveryCache cache) : IConfigureNamedOptions { public void Configure(string? name, ClientCredentialsClient options) { if (name == "invoices") { var disco = cache.GetAsync().GetAwaiter().GetResult(); options.TokenEndpoint = new Uri(disco.TokenEndpoint); options.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); options.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); options.Scope = Scope.Parse("list"); options.Resource = Resource.Parse("urn:invoices"); } } public void Configure(ClientCredentialsClient options) { // implement default configure Configure("", options); } } ``` * V3 ClientCredentialsClientConfigureOptions.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; public class ClientCredentialsClientConfigureOptions(DiscoveryCache cache) : IConfigureNamedOptions { public void Configure(string? name, ClientCredentialsClient options) { if (name == "invoices") { var disco = cache.GetAsync().GetAwaiter().GetResult(); options.TokenEndpoint = disco.TokenEndpoint; options.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; options.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; options.Scope = "list"; options.Resource = "urn:invoices"; } } public void Configure(ClientCredentialsClient options) { // implement default configure Configure("", options); } } ``` You will also need to register the config options, for example: Program.cs ```csharp services.AddClientCredentialsTokenManagement(); services.AddSingleton(new DiscoveryCache("https://sts.company.com")); services.AddSingleton, ClientCredentialsClientConfigureOptions>(); ``` ## Backchannel Communication [Section titled “Backchannel Communication”](#backchannel-communication) By default, all backchannel communication will be done using a named client from the HTTP client factory. The name is `Duende.AccessTokenManagement.BackChannelHttpClient` which is also a constant called `ClientCredentialsTokenManagementDefaults.BackChannelHttpClientName`. You can register your own HTTP client with the factory using the above name and thus provide your own custom HTTP client. The client registration object has two additional properties to customize the HTTP client: * `HttpClientName` - if set, this HTTP client name from the factory will be used instead of the default one * `HttpClient` - allows setting an instance of `HttpClient` to use. Will take precedence over a client name ## Token caching [Section titled “Token caching”](#token-caching) * V4 In V4, access tokens are cached using [`HybridCache`](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/overview?view=aspnetcore-9.0#hybridcache). ### Using remote caches [Section titled “Using remote caches”](#using-remote-caches) Hybrid cache is a 2 tier cache, with in-memory and remote capabilities. Hybrid cache automatically picks up any IDistributedCache implementation as it’s remote cache. See [Distributed Caching in Asp.Net](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-9.0) on more information on topic. ### Injecting a custom cache [Section titled “Injecting a custom cache”](#injecting-a-custom-cache) By default, we use the default HybridCache implementation. You may want to inject a custom hybrid cache implementation, such as [FusionCache](https://github.com/ZiggyCreatures/FusionCache). You can do this either for the entire system: Program.cs ```csharp services.AddSingletonHybridCache>(new MyCustomCacheImplementation()); ``` Or only for `Duende.AccessTokenManagement`, by using Service Keys: Program.cs ```csharp services.AddSingletonHybridCache>(ServiceProviderKeys.ClientCredentialsTokenCache, new MyCustomCacheImplementation()); ``` ### Customizing cache keys [Section titled “Customizing cache keys”](#customizing-cache-keys) By default, cache keys are built up as follows: `{options.CacheKeyPrefix}::{client_name}::hashed({scope})::hashed({resource})` * `options.CacheKeyPrefix` can be configured using the `ClientCredentialsTokenManagementOptions` * `client_name` is the name of the client * `scope` is the scope parameter (if any) that’s used to request the access token. * `resource` is the resource parameter (if any) that’s used to request the access token. The values of both the `scope` and `resource` hashed (MD5) to ensure that the cache key length is not unbounded. You can implement your own cache key generator by implementing a custom `IClientCredentialsCacheKeyGenerator` and registering this to your service container. This is needed if you’re adding custom parameters to your `TokenRequestParameters` ### Encrypting cache entries [Section titled “Encrypting cache entries”](#encrypting-cache-entries) You may want to share a remote cache with other parts of the application or even with other applications. In that case, it may be wise to encrypt the access tokens in the remote cache. You can achieve this with a custom serializer. Program.cs ```csharp // Explicitly register a serializer for the client credentials tokens with the hybrid cache services.AddHybridCache() .AddSerializer(); // This example uses data protection api. You'll want to configure this to suit your needs services.AddDataProtection(); /// /// Example on how to implement a serializer that encrypts data using ASP.NET Core Data Protection. /// public class EncryptedHybridCacheSerializer : IHybridCacheSerializer { private readonly IDataProtector _protector; public EncryptedHybridCacheSerializer(IDataProtectionProvider provider) { _protector = provider.CreateProtector("ClientCredentialsToken"); } public ClientCredentialsToken Deserialize(ReadOnlySequence source) { // Convert the sequence to a byte array var buffer = source.ToArray(); // Unprotect (decrypt) the data var unprotected = _protector.Unprotect(buffer); // Deserialize the JSON payload return JsonSerializer.Deserialize(unprotected)!; } public void Serialize(ClientCredentialsToken value, IBufferWriter target) { // Serialize the value to JSON var json = JsonSerializer.SerializeToUtf8Bytes(value); // Protect (encrypt) the data var protectedBytes = _protector.Protect(json); // Write to the buffer target.Write(protectedBytes); } } ``` * V3 By default, tokens will be cached using the `IDistributedCache` abstraction in ASP.NET Core. You can either use the in-memory cache version, or a real distributed cache like Redis. For development purposes, you can use the `MemoryDistributedCache`: Program.cs ```csharp services.AddDistributedMemoryCache(); ``` Note that `MemoryDistributedCache` will be cleared whenever the process is restarted. It won’t be shared between multiple instances of your application in a load-balanced environment. As a result, a new token will have to be obtained when you restart your application, and each instance will obtain a different token. For production deployments, we recommend using a [distributed cache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed#establish-distributed-caching-services). The built-in cache in `Duende.AccessTokenManagment` uses two settings from the options, which apply with any `IDistributedCache`: Program.cs ```csharp services.AddClientCredentialsTokenManagement(options => { options.CacheLifetimeBuffer = 60; options.CacheKeyPrefix = "Duende.AccessTokenManagement.Cache::"; }); ``` `CacheLifetimeBuffer` is a value in seconds that will be subtracted from the token lifetime, e.g. if a token is valid for one hour, it will be cached for 59 minutes only. The cache key prefix is used to construct the unique key for the cache item based on client name, requested scopes and resource. Finally, you can also replace the caching implementation altogether by registering your own `IClientCredentialsTokenCache`.
-----
# Demonstrating Proof-of-Possession (DPoP)
> Demonstrating Proof-of-Possession is a security mechanism that binds access tokens to specific cryptographic keys to prevent token theft and misuse.
[DPoP](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop) specifies how to bind an asymmetric key stored within a JSON Web Key (JWK) to an access token. This will make the access token bound to the key such that if the access token were to leak, it cannot be used without also having access to the private key of the corresponding JWK. The Duende.AccessTokenManagement library supports DPoP. ## DPoP Key [Section titled “DPoP Key”](#dpop-key) The main piece that your hosting application needs to concern itself with is how to get (and manage) the DPoP key. This key (and signing algorithm) will be either an “RS”, “PS”, or “ES” style key, and needs to be in the form of a JSON Web Key (or JWK). Consult the specification for more details. The creation and management of this DPoP key is up to the policy of the client. For example is can be dynamically created when the client starts up, and can be periodically rotated. The main constraint is that it must be stored for as long as the client uses any access tokens (and possibly refresh tokens) that they are bound to, which this library will manage for you. Creating a JWK in .NET is simple: Program.cs ```csharp using System.Security.Cryptography; using System.Text.Json; using Microsoft.IdentityModel.Tokens; var rsaKey = new RsaSecurityKey(RSA.Create(2048)); var jwkKey = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey); jwkKey.Alg = "PS256"; var jwk = JsonSerializer.Serialize(jwkKey); Console.WriteLine(jwk); ``` ## Key Configuration [Section titled “Key Configuration”](#key-configuration) Once you have a JWK you wish to use, then it must be configured or made available to this library. That can be done in one of two ways: * Configure the key at startup by setting the `DPoPJsonWebKey` property on either the `ClientCredentialsTokenManagementOptions` or `UserTokenManagementOptions` (depending on which of the two styles you are using from this library). * Implement the `IDPoPKeyStore` interface to produce the key at runtime. Here’s a sample configuring the key in an application using `AddOpenIdConnectAccessTokenManagement` in the startup code: Program.cs ```csharp services.AddOpenIdConnectAccessTokenManagement(options => { options.DPoPJsonWebKey = jwk; }); ``` Similarly, for an application using `AddClientCredentialsTokenManagement`, it would look like this: Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("client_name", options => { options.DPoPJsonWebKey = jwk; }); ``` ## Proof Tokens At The Token Server’s Token Endpoint [Section titled “Proof Tokens At The Token Server’s Token Endpoint”](#proof-tokens-at-the-token-servers-token-endpoint) Once the key has been configured for the client, then the library will use it to produce a DPoP proof token when calling the token server (including token renewals if relevant). There is nothing explicit needed on behalf of the developer using this library. ### `dpop_jkt` At The Token Server’s Authorize Endpoint [Section titled “dpop\_jkt At The Token Server’s Authorize Endpoint”](#dpop_jkt-at-the-token-servers-authorize-endpoint) When using DPoP and `AddOpenIdConnectAccessTokenManagement`, this library will also automatically include the `dpop_jkt` parameter to the authorize endpoint. ## Proof Tokens At The API [Section titled “Proof Tokens At The API”](#proof-tokens-at-the-api) Once the library has gotten a DPoP bound access token for the client, then if your application is using any of the `HttpClient` client factory helpers (e.g. `AddClientCredentialsHttpClient` or `AddUserAccessTokenHttpClient`) then those outbound HTTP requests will automatically include a DPoP proof token for the associated DPoP access token. ## Considerations [Section titled “Considerations”](#considerations) A point to keep in mind when using DPoP and `AddOpenIdConnectAccessTokenManagement` is that the DPoP proof key is created per user session. This proof key must be store somewhere, and the `AuthenticationProperties` used by both the OIDC and cookie handlers is what is used to store this key. This implies that the OIDC `state` parameter will increase in size, as well the resultant cookie that represents the user’s session. The storage for each of these can be customized with the properties on the options `StateDataFormat` and `SessionStore` respectively.
-----
# Extensibility
> Learn how to extend and customize Duende.AccessTokenManagement, including custom token retrieval.
There are several extension points where you can customize the behavior of Duende.AccessTokenManagement. The extension model is designed to favor composition over inheritance, making it easier to customize and extend while maintaining the library’s core functionality. ## Token Retrieval [Section titled “Token Retrieval”](#token-retrieval) Token retrieval can be customized by implementing the `AccessTokenRequestHandler.ITokenRetriever` interface. This interface defines a single method, `GetTokenAsync`, which is called by the `AccessTokenRequestHandler` to retrieve an access token. A common scenario for this would be if you wanted to implement a different token retrieval flow, that’s currently not implemented, such as [Impersonation or Delegation grants (RFC 8693)](https://datatracker.ietf.org/doc/html/rfc8693). Implementing this particular flow is outside the scope of this document. The following snippet demonstrates how to implement fictive scenario where a custom token retriever dynamically determines which credential flow to use. CustomTokenRetriever.cs ```csharp public class CustomTokenRetriever( UserTokenRequestParameters parameters, IClientCredentialsTokenManager clientCredentialsTokenManager, IUserTokenManager userTokenManagement, IUserAccessor userAccessor, ClientCredentialsClientName clientName) : AccessTokenRequestHandler.ITokenRetriever { public async Task> GetTokenAsync( HttpRequestMessage request, CancellationToken ct) { // You'll have to make a decision on what token parameters to use, // and you can override the default parameters. var param = parameters with { Scope = Scope.Parse("some scope"), ForceTokenRenewal = request.GetForceRenewal() // for retry policies. }; AccessTokenRequestHandler.IToken token; // Get the type from current context. // Using a random number as an example here. int tokenType = new Random().Next(1, 2); if (tokenType == 1) { var getTokenResult = await clientCredentialsTokenManager .GetAccessTokenAsync(clientName, param, ct); if (!getTokenResult.Succeeded) { return getTokenResult.FailedResult; } token = getTokenResult.Token; } else { var user = await userAccessor.GetCurrentUserAsync(ct); var getTokenResult = await userTokenManagement .GetAccessTokenAsync(user, param, ct); if (!getTokenResult.Succeeded) { return getTokenResult.FailedResult; } token = getTokenResult.Token; } return TokenResult.Success(token); } } ``` A custom token handler can be linked to your `HttpClient` by creating an `AccessTokenRequestHandler` and adding it to the request pipeline: Program.cs ```csharp services.AddHttpClient() .AddDefaultAccessTokenResiliency() .AddHttpMessageHandler(provider => { var yourCustomTokenRetriever = new CustomTokenRetriever(...); var logger = provider.GetRequiredService>(); var dPoPProofService = provider.GetRequiredService(); var dPoPNonceStore = provider.GetRequiredService(); return new AccessTokenRequestHandler( tokenRetriever: yourCustomTokenRetriever, dPoPNonceStore: dPoPNonceStore, dPoPProofService: dPoPProofService, logger: logger); }); ``` ## Token Request Customization [Section titled “Token Request Customization”](#token-request-customization) Token request parameters can be customized by implementing the `ITokenRequestCustomizer` interface. This interface allows you to dynamically modify token request parameters based on the incoming HTTP request context, making it ideal for multi-tenant applications where token parameters need to vary per tenant. The customizer is invoked before token retrieval and works with both user and client credentials flows. Unlike implementing a custom token retriever, which replaces the entire token acquisition logic, the customizer focuses on modifying parameters such as scopes, resources or other parts of the `TokenRequestParameters`. ### Multi-Tenant Scenario [Section titled “Multi-Tenant Scenario”](#multi-tenant-scenario) In multi-tenant applications, different tenants often require different parameters. For example, each tenant might have: * A unique API resource or audience identifier * Tenant-specific scopes The `ITokenRequestCustomizer` provides a clean way to handle these variations without needing separate `HttpClient` configurations for each tenant. The following example demonstrates a multi-tenant scenario where the customizer extracts the tenant identifier from the HTTP request and applies tenant-specific token parameters: MultiTenantTokenRequestCustomizer.cs ```csharp public class MultiTenantTokenRequestCustomizer( ITenantResolver tenantResolver, ITenantConfigurationStore tenantConfigStore) : ITokenRequestCustomizer { public async Task Customize( HttpRequestMessage httpRequest, TokenRequestParameters baseParameters, CancellationToken cancellationToken) { // Extract tenant identifier from the request // This could come from a header, subdomain, or route parameter var tenantId = await tenantResolver.GetTenantIdAsync(httpRequest, cancellationToken); // Get tenant-specific configuration var tenantConfig = await tenantConfigStore.GetConfigurationAsync(tenantId, cancellationToken); // Customize parameters with tenant-specific values return baseParameters with { Resource = Resource.Parse(tenantConfig.ApiResource), Scope = Scope.Parse(tenantConfig.RequiredScopes), // Add any additional customizations }; } } ``` An instance of the `ITokenRequestCustomizer` implementation can be registered as part of the call to the `Add*Handler` methods: Program.cs ```csharp var customizer = new MultiTenantTokenRequestCustomizer(tenantResolver, tenantConfigStore); // Client Credentials Token Handler services.AddHttpClient("client-credentials-token-http-client") .AddClientCredentialsTokenHandler(customizer, ClientCredentialsClientName.Parse("pure-client-credentials")); // User Access Token Handler services.AddHttpClient("user-access-token-http-client") .AddUserAccessTokenHandler(customizer); // Client Access Token Handler services.AddHttpClient("client-access-token-http-client") .AddClientAccessTokenHandler(customizer); ``` If you require access to services from the service provider, you can use the `Add*Handler` method overloads that accept a factory delegate: Program.cs ```csharp builder.Services.AddScoped(); // Client Credentials Token Handler services.AddHttpClient("client-credentials-token-http-client") .AddClientCredentialsTokenHandler( serviceProvider => serviceProvider.GetRequiredService(), ClientCredentialsClientName.Parse("pure-client-credentials")); // User Access Token Handler services.AddHttpClient("user-access-token-http-client") .AddUserAccessTokenHandler( serviceProvider => serviceProvider.GetRequiredService()); // Client Access Token Handler services.AddHttpClient("client-access-token-http-client") .AddClientAccessTokenHandler( serviceProvider => serviceProvider.GetRequiredService()); ``` ### Additional Use Cases [Section titled “Additional Use Cases”](#additional-use-cases) Beyond multi-tenancy, `ITokenRequestCustomizer` can be used for: * Dynamically setting scopes based on the target API endpoint * Adding audience or resource parameters based on request headers or route data * Implementing per-request token parameter logic without changing the core retrieval flow
-----
# Logging
> Documentation for logging configuration and usage in Duende Access Token Management, including log levels and Serilog setup
Duende Access Token Management uses the standard logging facilities provided by ASP.NET Core. You generally do not need to perform any extra configuration, as it will use the logging provider you have already configured for your application. For general information on how to configure logging, setting up Serilog, and understanding log levels in Duende products, see our [Logging Fundamentals](/general/logging/) guide. The Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) has a good introduction and description of the built-in logging providers. ## Log Levels [Section titled “Log Levels”](#log-levels) You can control the log output for Duende Access Token Management specifically by configuring the `Duende.AccessTokenManagement` namespace in your logging configuration. For example, to enable debug logging for Access Token Management while keeping other logs at a higher level, you can modify your `appsettings.json`: appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Duende.AccessTokenManagement": "Debug" } } } ```
-----
# Customizing User Token Management
> Learn how to customize user token management options, per-request parameters, and token storage mechanisms in ASP.NET Core applications.
The most common way to use [access token management is for interactive web applications](/accesstokenmanagement/web-apps/) - however, you may want to customize certain aspects of it. Here’s what you can do. ## General Options [Section titled “General Options”](#general-options) You can pass in some global options when registering token management in the ASP.NET Core service provider. * `ChallengeScheme` - by default the OIDC configuration is inferred from the default challenge scheme. This is recommended approach. If for some reason your OIDC handler is not the default challenge scheme, you can set the scheme name on the options * `UseChallengeSchemeScopedTokens` - the general assumption is that you only have one OIDC handler configured. If that is not the case, token management needs to maintain multiple sets of token artefacts simultaneously. You can opt in to that feature using this setting. * `ClientCredentialsScope` - when requesting client credentials tokens from the OIDC provider, the scope parameter will not be set since its value cannot be inferred from the OIDC configuration. With this setting you can set the value of the scope parameter. * `ClientCredentialsResource` - same as previous, but for the resource parameter * `ClientCredentialStyle` - specifies how client credentials are transmitted to the OIDC provider Program.cs ```csharp builder.Services.AddOpenIdConnectAccessTokenManagement(options => { options.ChallengeScheme = "schemeName"; options.UseChallengeSchemeScopedTokens = false; options.ClientCredentialsScope = "api1 api2"; options.ClientCredentialsResource = "urn:resource"; options.ClientCredentialStyle = ClientCredentialStyle.PostBody; }); ``` ## Per Request Parameters [Section titled “Per Request Parameters”](#per-request-parameters) You can also modify token management parameters on a per-request basis. The `UserTokenRequestParameters` class can be used for that: * `SignInScheme` - allows specifying a sign-in scheme. This is used by the default token store * `ChallengeScheme` - allows specifying a challenge scheme. This is used to infer token service configuration * `ForceRenewal` - forces token retrieval even if a cached token would be available * `Scope` - overrides the globally configured scope parameter * `Resource` - override the globally configured resource parameter * `Assertion` - allows setting a client assertion for the request The request parameters can be passed via the manual API: ```csharp var token = await _tokenManagementService .GetAccessTokenAsync(User, new UserAccessTokenRequestParameters { // ... }); ``` …the extension methods ```csharp var token = await HttpContext.GetUserAccessTokenAsync( new UserTokenRequestParameters { // ... }); ``` …or the HTTP client factory Program.cs ```csharp // registers HTTP client that uses the managed user access token builder.Services.AddUserAccessTokenHttpClient("invoices", parameters: new UserTokenRequestParameters { // ... }, configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }); // registers a typed HTTP client with token management support builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }) .AddUserAccessTokenHandler(new UserTokenRequestParameters { // ... }); ``` ## Token Storage [Section titled “Token Storage”](#token-storage) By default, the user’s access and refresh token will be store in the ASP.NET Core authentication session (implemented by the cookie handler). You can modify this in two ways * the cookie handler itself has an extensible storage mechanism via the `TicketStore` mechanism * replace the store altogether by providing an `IUserTokenStore` implementation and registering it in the service provider at application startup
-----
# Blazor Server Access Token Management
> Learn how to manage access tokens in Blazor Server applications and handle token storage and HTTP client usage with Duende.AccessTokenManagement.
Blazor Server applications have the same token management requirements as a regular ASP.NET Core web application. Because Blazor Server streams content to the application over a websocket, there often is no HTTP request or response to interact with during the execution of a Blazor Server application. You therefore cannot use `HttpContext` in a Blazor Server application as you would in a traditional ASP.NET Core web application. This means: * you cannot use `HttpContext` extension methods * you cannot use the ASP.NET authentication session to store tokens * the normal mechanism used to automatically attach tokens to Http Clients making API calls won’t work Fortunately, `Duende.AccessTokenManagement` provides a straightforward solution to these problems. Also see the [*BlazorServer* sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/BlazorServer) for source code of a full example. ## Token Storage [Section titled “Token Storage”](#token-storage) Since the tokens cannot be managed in the authentication session, you need to store them somewhere else. The options include an in-memory data structure, a distributed cache like redis, or a database. Duende.AccessTokenManagement describes this store for tokens with the *IUserTokenStore* interface. In non-blazor scenarios, the default implementation that stores the tokens in the session is used. In your Blazor server application, you’ll need to decide where you want to store the tokens and implement the store interface. The store interface is straightforward. `StoreTokenAsync` adds a token to the store for a particular user, `GetTokenAsync` retrieves the user’s token, and *ClearTokenAsync* clears the tokens stored for a particular user. A sample implementation that stores the tokens in memory can be found in the *ServerSideTokenStore* in the [*BlazorServer* sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/BlazorServer). Register your token store in the ASP.NET Core service provider and tell Duende.AccessTokenManagement to integrate with Blazor by calling `AddBlazorServerAccessTokenManagement`: Program.cs ```csharp builder.Services.AddOpenIdConnectAccessTokenManagement() .AddBlazorServerAccessTokenManagement(); ``` Once you’ve registered your token store, you need to use it. You initialize the token store with the `TokenValidated` event in the OpenID Connect handler: OidcEvents.cs ```csharp public class OidcEvents : OpenIdConnectEvents { private readonly IUserTokenStore _store; public OidcEvents(IUserTokenStore store) { _store = store; } public override async Task TokenValidated(TokenValidatedContext context) { var exp = DateTimeOffset.UtcNow.AddSeconds(double.Parse(context.TokenEndpointResponse!.ExpiresIn)); await _store.StoreTokenAsync(context.Principal!, new UserToken { AccessToken = AccessToken.Parse(context.TokenEndpointResponse.AccessToken), AccessTokenType = AccessTokenType.Parse(context.TokenEndpointResponse.TokenType), RefreshToken = RefreshToken.Parse(context.TokenEndpointResponse.RefreshToken), Scope = Scope.Parse(context.TokenEndpointResponse.Scope), IdentityToken = IdentityToken.Parse(context.TokenEndpointResponse.IdToken), Expiration = exp, }); await base.TokenValidated(context); } } ``` Once registered and initialized, `Duende.AccessTokenManagement` will keep the store up to date automatically as tokens are refreshed. ## Retrieving And Using Tokens [Section titled “Retrieving And Using Tokens”](#retrieving-and-using-tokens) If you’ve registered your token store with `AddBlazorServerAccessTokenManagement`, Duende.AccessTokenManagement will register the services necessary to attach tokens to outgoing HTTP requests automatically, using the same API as a non-blazor application. You inject an HTTP client factory and resolve named HTTP clients where ever you need to make HTTP requests, and you register the HTTP client’s that use access tokens in the ASP.NET Core service provider with our extension method: Program.cs ```csharp builder.Services.AddUserAccessTokenHttpClient("demoApiClient", configureClient: client => { client.BaseAddress = new Uri("https://demo.duendesoftware.com/api/"); }); ```
-----
# Duende AccessTokenManagement v3.x to v4.0
> Guide for upgrading Duende.AccessTokenManagement from version 3.x to version 4.0, including migration steps for custom implementations and breaking changes.
## Changes [Section titled “Changes”](#changes) ### Moving Towards HybridCache Implementation And Away from Distributed Cache [Section titled “Moving Towards HybridCache Implementation And Away from Distributed Cache”](#moving-towards-hybridcache-implementation-and-away-from-distributed-cache) Microsoft has recently released [HybridCache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid). While this is only released as a .NET 9 assembly, these assemblies work fine in .NET 8. So, while we still support .NET 8 with ATM 4.0, we are moving towards using HybridCache. HybridCache brings significant improvements for us. Because of the two-layered cache, we’ve found it significantly improves performance.\ If you currently use a distributed cache, this should still work seamlessly. If you wish to encrypt access tokens, you can do so by implementing a custom serializer. Documentation on this will follow later. We have added support for using a custom HybridCache instance via keyed services. ### Complete Internal Refactoring [Section titled “Complete Internal Refactoring”](#complete-internal-refactoring) The library has undergone extensive internal changes—so much so that it can be considered a new implementation under the same conceptual umbrella. Despite this, the public API surface remains mostly compatible with earlier versions. * New extensibility model (see below). * All async methods now support cancellation tokens. * Renaming of certain classes and interfaces (see below). * Implementation logic is now internal. #### Reduced Public API Surface [Section titled “Reduced Public API Surface”](#reduced-public-api-surface) All internal implementation details are now marked as internal, reducing accidental coupling and clarifying the intended extension points. In V3, all classes were public and most public methods were marked as virtual. This meant you could override any class by inheriting from it and overriding a single method. While this was very convenient for our consumers, it made it challenging to introduce changes to the library without making breaking changes. We still want to ensure our users’ extensibility needs are met, but via more controlled mechanisms. If you find that you have an extensibility need not covered by the new model, please raise a discussion [in our discussion board](https://duende.link/community). If this is a scenario we want to support, we’ll do our best to accommodate it. ### Explicit Extension Model [Section titled “Explicit Extension Model”](#explicit-extension-model) Instead of relying on implicit behaviors or inheritance, V4 introduces clearly defined extension points, making it easier to customize behavior without relying on internal details. ### Composition Over Inheritance [Section titled “Composition Over Inheritance”](#composition-over-inheritance) The `AccessTokenHandler` has been restructured to use composition rather than inheritance, simplifying the customization of token handling and increasing testability. As part of these changes, the retry logic that was part of the `AccessTokenHandler`’s functionality in V3 has moved to a resiliency policy. If you’re not using `AddClientCredentialsHttpClient` to configure an HTTP client, and you depend on the retry policy to be there, you should use `AddDefaultAccessTokenResiliency` to add our implementation or provide your own retry policy. See [Service Workers](/accesstokenmanagement/workers/) for more details. If you wish to implement a custom access token handling process, for example to implement token exchange, you can now [implement your own `AccessTokenRequestHandler.ITokenRetriever`](/accesstokenmanagement/advanced/extensibility/#token-retrieval). ### Strongly Typed Configuration [Section titled “Strongly Typed Configuration”](#strongly-typed-configuration) Configuration is now represented by strongly typed objects, improving validation, discoverability, and IDE support. This means that where before you could assign strings to the configuration system, you’ll now have to explicitly parse the string values. For example: ```csharp var scheme = Scheme.Parse("oidc"); ``` ### Renamed classes [Section titled “Renamed classes”](#renamed-classes) Several classes have been renamed, either to clarify their usage or to drop the `service` suffix, which only adds noise: * `AccessTokenHandler` is now `AccessTokenRequestHandler` * `IClientCredentialsTokenManagementService` is now `IClientCredentialsTokenManager` * `IClientCredentialsTokenEndpointService` is now `IClientCredentialsTokenEndpoint` * `IUserTokenManagementService` is now `IUserTokenManager` * `ITokenRequestSynchronization` is now `IUserTokenRequestConcurrencyControl` * `IUserTokenEndpointService` is now `IOpenIdConnectUserTokenEndpoint`
-----
# Web Applications
> Learn how to manage access tokens in web applications, including setup, configuration, and usage with HTTP clients.
The `Duende.AccessTokenManagement.OpenIdConnect` library automates all the tasks around access token lifetime management for user-centric web applications. While many of the details can be customized, by default the following is assumed: * ASP.NET Core web application * cookie authentication handler for session management * OpenID Connect authentication handler for authentication and access token requests against an OpenID Connect compliant token service * the token service returns a refresh token Using this library, you can either request `user access tokens` or `client credentials tokens`. User access tokens typically contain information about the currently logged in user, such as the `sub` claim. They are used to access services under the credentials of the currently logged in user. `Client credentials tokens` do not contain information about the currently logged in user and are typically used to do machine-to-machine calls. To get started, you’ll need to add `Duende.AccessTokenManagement.OpenIdConnect` to your solution. Then, there are two fundamental ways to interact with token management: 1. **Automatic** recommended: You request a http client from the IHTTPClientFactory. This http client automatically requests, optionally renews and attaches the access tokens on each request. 2. **Manually** advanced: You request an access token, which you can then use to (for example) authenticate with services. You are responsible for attaching the access token to requests. Let’s look at these steps in more detail. ## Adding AccessTokenManagement To Your Project [Section titled “Adding AccessTokenManagement To Your Project”](#adding-accesstokenmanagement-to-your-project) To use this library, start by adding the library to your .NET projects. ```bash dotnet add package Duende.AccessTokenManagement.OpenIdConnect ``` By default, the token management library will use the ASP.NET Core default authentication scheme for token storage. This is typically the cookie handler and its authentication session. It also used the default challenge scheme for deriving token client configuration for refreshing tokens or requesting client credential tokens (typically the OpenID Connect handler pointing to your trusted authority). Program.cs ```csharp // setting up default schemes and handlers builder.Services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "web"; // automatically revoke refresh token at signout time options.Events.OnSigningOut = async e => { await e.HttpContext.RevokeRefreshTokenAsync(); }; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://sts.company.com"; options.ClientId = "webapp"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.Scope.Clear(); // OIDC related scopes options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); // API scopes options.Scope.Add("invoice"); options.Scope.Add("customer"); // requests a refresh token options.Scope.Add("offline_access"); options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; // important! this store the access and refresh token in the authentication session // this is needed to the standard token store to manage the artefacts options.SaveTokens = true; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; }); // adds services for token management builder.Services.AddOpenIdConnectAccessTokenManagement(); ``` ## Automatic Via HTTP Client Factory [Section titled “Automatic Via HTTP Client Factory”](#automatic-via-http-client-factory) Similar to the worker service support, you can register HTTP clients that automatically send the access token of the current user when making API calls. The message handler associated with those HTTP clients will try to make sure, the access token is always valid and not expired. Program.cs ```csharp // registers HTTP client that uses the managed user access token builder.Services.AddUserAccessTokenHttpClient("invoices", configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }); ``` This could be also a typed client: Program.cs ```csharp // registers a typed HTTP client with token management support builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }) .AddUserAccessTokenHandler(); ``` Of course, the ASP.NET Core web application host could also do machine to machine API calls that are independent of a user. In this case all the token client configuration can be inferred from the OpenID Connect handler configuration. The following registers an HTTP client that uses a client credentials token for outgoing calls: ```csharp // registers HTTP client that uses the managed client access token builder.Services.AddClientAccessTokenHttpClient("masterdata.client", configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/masterdata/"); }); ``` As a typed client: Program.cs ```csharp builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/masterdata/"); }) .AddClientAccessTokenHandler(); ``` Last but not least, if you registered clients with the factory, you can use them. They will try to make sure that a current access token is always sent along. If that is not possible, ultimately a `401` HTTP status code will be returned to the calling code. ```csharp public async Task CallApi() { var client = _httpClientFactory.CreateClient("invoices"); var response = await client.GetAsync("list"); // rest omitted } ``` …or for a typed client: ```csharp public async Task CallApi([FromServices] InvoiceClient client) { var response = await client.GetList(); // rest omitted } ``` ### gRPC Support [Section titled “gRPC Support”](#grpc-support) If you are using gRPC, you can also use the `AddClientAccessTokenHandler` and `AddUserAccessTokenHandler` methods when registering typed gRPC clients: Program.cs ```csharp builder.Services.AddGrpcClient(o => { o.Address = new Uri("https://localhost:5001"); }) .AddUserAccessTokenHandler(); // or .AddClientAccessTokenHandler(); when using client credentials ``` ## Manually Request Access Tokens [Section titled “Manually Request Access Tokens”](#manually-request-access-tokens) If you want to use access tokens differently or have more advanced needs which the automatic option doesn’t cover, then you can also manually request user access tokens. * V4 You can get the current user access token manually by writing code against the `IUserTokenManager`. Program.cs ```csharp public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IUserTokenManager _userTokenManager; public HomeController(IHttpClientFactory httpClientFactory, IUserTokenManager userTokenManager) { _httpClientFactory = httpClientFactory; _userTokenManager = userTokenManager; } public async Task CallApi(CancellationToken ct) { var token = await _userTokenManager.GetAccessTokenAsync(User, ct: ct); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices", ct); // rest omitted } } ``` * V3 You can get the current user access token manually by writing code against the `IUserTokenManagementService`. Program.cs ```csharp public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IUserTokenManagementService _tokenManagementService; public HomeController(IHttpClientFactory httpClientFactory, IUserTokenManagementService tokenManagementService) { _httpClientFactory = httpClientFactory; _tokenManagementService = tokenManagementService; } public async Task CallApi() { var token = await _tokenManagementService.GetAccessTokenAsync(User); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices"); // rest omitted } } ``` ### HTTP Context Extension Methods [Section titled “HTTP Context Extension Methods”](#http-context-extension-methods) Alternatively, you can also manually request access tokens via these extension methods on the `HttpContext`: * `GetUserAccessTokenAsync` - returns an access token representing the user. If the current access token is expired, it will be refreshed. * `GetClientAccessTokenAsync` - returns an access token representing the client. If the current access token is expired, a new one will be requested * `RevokeRefreshTokenAsync` - revokes the refresh token ```csharp public async Task CallApi() { var token = await HttpContext.GetUserAccessTokenAsync(); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices"); // rest omitted } ```
-----
# Service Workers and Background Tasks
> Learn how to manage OAuth access tokens in worker applications and background tasks using Duende.AccessTokenManagement.
A common scenario in worker applications or background tasks (or really any daemon-style applications) is to call APIs using an OAuth token obtained via the client credentials flow. The access tokens need to be requested and [cached](/accesstokenmanagement/advanced/client-credentials/#token-caching) (either locally or shared between multiple instances) and made available to the code calling the APIs. In case of expiration (or other token invalidation reasons), a new access token needs to be requested. The actual business code should not need to be aware of this. For more information, see the [advanced topic on client credentials](/accesstokenmanagement/advanced/client-credentials/). To get started, you will need to add the `Duende.AccessTokenManagement` package to your solution. Next, there are two fundamental ways to interact with token management: 1. **Automatic** recommended: You request an `HttpClient` from the `IHttpClientFactory`. This HTTP client automatically requests, optionally renews and attaches the access tokens on each request. 2. **Manually** advanced: You request an access token, which you can then use to (for example) authenticate with services. You are responsible for attaching the access token to requests. Let’s cover these steps in more detail. ## Adding Duende.AccessTokenManagement [Section titled “Adding Duende.AccessTokenManagement”](#adding-duendeaccesstokenmanagement) Start by adding a reference to the `Duende.AccessTokenManagement` NuGet package to your application. ```bash dotnet add package Duende.AccessTokenManagement ``` You can add the necessary services to the ASP.NET Core service provider by calling `AddClientCredentialsTokenManagement()`. After that you can add one or more named client definitions by calling `AddClient`. * V4 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient(ClientCredentialsClientName.Parse("catalog.client"), client => { client.TokenEndpoint = new Uri("https://demo.duendesoftware.com/connect/token"); client.ClientId = ClientId.Parse("6f59b670-990f-4ef7-856f-0dd584ed1fac"); client.ClientSecret = ClientSecret.Parse("d0c17c6a-ba47-4654-a874-f6d576cdf799"); client.Scope = Scope.Parse("catalog inventory"); }) .AddClient(ClientCredentialsClientName.Parse("invoice.client"), client => { client.TokenEndpoint = new Uri("https://demo.duendesoftware.com/connect/token"); client.ClientId = ClientId.Parse("ff8ac57f-5ade-47f1-b8cd-4c2424672351"); client.ClientSecret = ClientSecret.Parse("4dbbf8ec-d62a-4639-b0db-aa5357a0cf46"); client.Scope = Scope.Parse("invoice customers"); }); ``` * V3 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("catalog.client", client => { client.TokenEndpoint = "https://demo.duendesoftware.com/connect/token"; client.ClientId = "6f59b670-990f-4ef7-856f-0dd584ed1fac"; client.ClientSecret = "d0c17c6a-ba47-4654-a874-f6d576cdf799"; client.Scope = "catalog inventory"; }) .AddClient("invoice.client", client => { client.TokenEndpoint = "https://demo.duendesoftware.com/connect/token"; client.ClientId = "ff8ac57f-5ade-47f1-b8cd-4c2424672351"; client.ClientSecret = "4dbbf8ec-d62a-4639-b0db-aa5357a0cf46"; client.Scope = "invoice customers"; }); // in v3, you explicitly need to add a distributed cache implementation, such as in-memory services.AddDistributedMemoryCache(); ``` ## Automatic Token Management Using HTTP Factory [Section titled “Automatic Token Management Using HTTP Factory”](#automatic-token-management-using-http-factory) You can register HTTP clients with the factory that will automatically use the above client definitions to request and use access tokens. The following code registers an `HttpClient` called `invoices` which automatically uses the `invoice.client` definition: * V4 Program.cs ```csharp services.AddClientCredentialsHttpClient("invoices", ClientCredentialsClientName.Parse("invoice.client"), client => { client.BaseAddress = new Uri("https://apis.company.com/invoice/"); }); ``` You can also set up a typed HTTP client to use a token client definition, e.g.: Program.cs ```csharp services.AddHttpClient(client => { client.BaseAddress = new Uri("https://apis.company.com/catalog/"); }) .AddClientCredentialsTokenHandler(ClientCredentialsClientName.Parse("catalog.client")); ``` * V3 Program.cs ```csharp services.AddClientCredentialsHttpClient("invoices", "invoice.client", client => { client.BaseAddress = new Uri("https://apis.company.com/invoice/"); }); ``` You can also set up a typed HTTP client to use a token client definition, e.g.: Program.cs ```csharp services.AddHttpClient(client => { client.BaseAddress = new Uri("https://apis.company.com/catalog/"); }) .AddClientCredentialsTokenHandler("catalog.client"); ``` Once you have set up HTTP clients in the HTTP factory, no token-related code is needed at all, e.g.: WorkerHttpClient.cs ```csharp public class WorkerHttpClient : BackgroundService { private readonly ILogger _logger; private readonly IHttpClientFactory _clientFactory; public WorkerHttpClient(ILogger logger, IHttpClientFactory factory) { _logger = logger; _clientFactory = factory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = _clientFactory.CreateClient("invoices"); var response = await client.GetAsync("test", stoppingToken); // rest omitted } } } ``` ## Manually Request Access Tokens [Section titled “Manually Request Access Tokens”](#manually-request-access-tokens) If you want to use access tokens in a different way or have more advanced needs which the automatic option doesn’t cover, then you can also manually request access tokens. * V4 You can retrieve the current access token for a given token client via `IClientCredentialsTokenManager.GetAccessTokenAsync`. WorkerManual.cs ```csharp public class WorkerManual( IHttpClientFactory factory, IClientCredentialsTokenManager tokenManagementService ) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = factory.CreateClient(); client.BaseAddress = new Uri("https://apis.company.com/catalog/"); // get access token for client and set on HttpClient var token = await tokenManagementService.GetAccessTokenAsync( ClientCredentialsClientName.Parse("catalog.client"), ct: stoppingToken) .GetToken(); client.SetBearerToken(token.AccessToken.ToString()); var response = await client.GetAsync("list", stoppingToken); // rest omitted } } } ``` The result of the GetAccessTokenAsync method is a `TokenResult`. You can interrogate this to see if the result was successful by checking the `Succeeded` property or by calling `WasSuccessful()`. Alternatively, as you see in the example, you can call the `.GetToken()` which will throw if the token couldn’t be retrieved. You can customize some of the per-request parameters by passing in an instance of `TokenRequestParameters`. This allows forcing a fresh token request (even if a cached token would exist) and also allows setting a per-request scope, resource and client assertion. * V3 You can retrieve the current access token for a given token client via `IClientCredentialsTokenManagementService.GetAccessTokenAsync`. WorkerManual.cs ```csharp public class WorkerManual : BackgroundService { private readonly IHttpClientFactory _clientFactory; private readonly IClientCredentialsTokenManagementService _tokenManagementService; public WorkerManual(IHttpClientFactory factory, IClientCredentialsTokenManagementService tokenManagementService) { _clientFactory = factory; _tokenManagementService = tokenManagementService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = _clientFactory.CreateClient(); client.BaseAddress = new Uri("https://apis.company.com/catalog/"); // get access token for client and set on HttpClient var token = await _tokenManagementService.GetAccessTokenAsync("catalog.client"); client.SetBearerToken(token.Value); var response = await client.GetAsync("list", stoppingToken); // rest omitted } } } ``` You can customize some of the per-request parameters by passing in an instance of `ClientCredentialsTokenRequestParameters`. This allows forcing a fresh token request (even if a cached token would exist) and also allows setting a per-request scope, resource and client assertion.