Client credentials token request from a Web host

In ASP.NET Core applications, you can securely call downstream APIs using IHttpClientFactory together with Duende's AccessTokenRequestHandler. This approach ensures that access tokens are automatically managed and refreshed when needed.

Code Sample

See the code lab for a working example: Call API from Web server host using IHttpClientFactory and Duende AccessTokenManagement

Flow Description

The diagrams below show how an HttpClient created by IHttpClientFactory uses Duende's token management to attach and refresh access tokens when calling a protected API.

  • The simplified diagram shows the main steps token retrieval, cache/expiration check, and API call.
  • The detailed diagram breaks down the internal calls between Duende components.

Simplified API call sequence with Duende.AccessTokenManagement

sequenceDiagram participant TestService as Service participant HttpClientFactory participant Duende.AccessTokenManagement participant TokenEndpoint as OIDC provider participant API as API TestService->>HttpClientFactory: CreateClient("m2m") Note left of Duende.AccessTokenManagement: Duende has an Http Delegation handler that <br> adds authorization header to the request, AccessTokenRequestHandler HttpClientFactory->>Duende.AccessTokenManagement: SendAsync(request) Note left of Duende.AccessTokenManagement: GetToken from cache and check expiration. alt Token expired or empty Note left of Duende.AccessTokenManagement: call configured token endpoint to get a new token with clientId and secret. Duende.AccessTokenManagement->>TokenEndpoint: POST /connect/token TokenEndpoint-->>Duende.AccessTokenManagement: Access token Duende.AccessTokenManagement-->>Duende.AccessTokenManagement: Update cahce with new token and add header else Token valid Duende.AccessTokenManagement-->>Duende.AccessTokenManagement: Add existing token to header end Note left of Duende.AccessTokenManagement: For DPoP DPoP header will also be added. Duende.AccessTokenManagement-->>HttpClientFactory: Attach token to Authorization header HttpClientFactory-->>TestService: Configured HttpClient TestService->>API: GET /api/v1/integration/health-records\n(with Bearer or DPoP token) API-->>TestService: Response

Detailed API call sequence with Duende.AccessTokenManagement

sequenceDiagram participant TestService participant HttpClientFactory participant AccessTokenRequestHandler as Duende.AccessTokenRequestHandler participant ITokenRetriever as Duende.ITokenRetriever participant IClientCredentialsTokenManager as Duende.IClientCredentialsTokenManager participant TokenEndpoint as IdentityServer participant API as Protected API TestService->>HttpClientFactory: CreateClient("m2m") HttpClientFactory->>AccessTokenRequestHandler: SendAsync(request) AccessTokenRequestHandler->>ITokenRetriever: Get token ITokenRetriever->>IClientCredentialsTokenManager: GetToken Note right of IClientCredentialsTokenManager: GetToken checks cache and expiry IClientCredentialsTokenManager->>IClientCredentialsTokenManager: Is token expired? alt Token expired IClientCredentialsTokenManager->>TokenEndpoint: POST /connect/token TokenEndpoint-->>IClientCredentialsTokenManager: Access token IClientCredentialsTokenManager-->>ITokenRetriever: New token else Token valid IClientCredentialsTokenManager-->>ITokenRetriever: Existing token end ITokenRetriever-->>AccessTokenRequestHandler: Token AccessTokenRequestHandler-->>HttpClientFactory: Attach token to Authorization header HttpClientFactory-->>TestService: Configured HttpClient TestService->>API: GET /api/v1/integration/health-records\n(with Bearer token) API-->>TestService: Response