# 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<ClientCredentialsClient>("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<ClientCredentialsClient>("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<ClientCredentialsClient>
  {
    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<ClientCredentialsClient>
  {
    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<IConfigureOptions<ClientCredentialsClient>,
  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<ClientCredentialsToken, EncryptedHybridCacheSerializer>();


  // This example uses data protection api. You'll want to configure this to suit your needs
  services.AddDataProtection();


  /// <summary>
  /// Example on how to implement a serializer that encrypts data using ASP.NET Core Data Protection.
  /// </summary>
  public class EncryptedHybridCacheSerializer : IHybridCacheSerializer<ClientCredentialsToken>
  {
      private readonly IDataProtector _protector;


      public EncryptedHybridCacheSerializer(IDataProtectionProvider provider)
      {
          _protector = provider.CreateProtector("ClientCredentialsToken");
      }


      public ClientCredentialsToken Deserialize(ReadOnlySequence<byte> 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<ClientCredentialsToken>(unprotected)!;
      }


      public void Serialize(ClientCredentialsToken value, IBufferWriter<byte> 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

```cs
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`.
-----