2.22Automatic Access Token Management

Created on Aug 17, 2026~23 min read

When integrating with third-party services (such as WeChat Official Accounts, WeChat Work, etc.), you usually need to obtain an Access Token first and carry that Access Token in subsequent requests to call the API normally. An Access Token has a validity period (usually two hours), after which it expires and must be re-obtained and updated.

To simplify this process, the framework has a built-in Access Token automatic management mechanism: when the Access Token does not exist or has expired, it automatically obtains a new Access Token and, according to the configuration, injects it into the request's Header, Query, Cookie, and other locations. It also supports automatically retrying when a request fails due to an invalid Access Token (such as returning 401).

The HttpAccessToken Model

HttpAccessToken represents Access Token information and contains the following constructors, properties, and methods:

  • Constructors:
    • new(value, expiresAt): Passes in the Access Token and its absolute expiration time (UTC time).
    • new(jwtToken): Passes in a JWT Token string.
  • Properties:
    • Value: The Access Token value (of type string).
    • ExpiresAt: The absolute expiration time of the Access Token (of type DateTimeOffset).
    • Scheme: The HTTP authentication scheme (of type string?).
    • RefreshToken: The refresh token (of type string?), internally providing convenient access based on Items["refresh_token"].
    • Items: A shared data dictionary (of type IDictionary<object, object?>) used to store custom data related to the Access Token (such as refresh_token, user identifiers, etc.).
  • Static Properties:
    • None: Indicates that there is no available Access Token (of type HttpAccessToken?).
  • Methods:
    • IsExpired() checks whether the Access Token has expired.
    • SetExpiresAt(expiresAt) sets the absolute expiration time of the Access Token.

Enablement Steps

1. Implement the IHttpAccessTokenProvider Interface

This interface is responsible for defining how to obtain and refresh the Access Token. All of its methods receive an HttpAccessTokenContext parameter, through which you can obtain custom data (such as username and password) passed in at request time via context.Items. Example:

cs
public sealed class WeiXinHttpAccessTokenProvider(IHttpRemoteService httpRemoteService) : IHttpAccessTokenProvider{    /// <inheritdoc />    public async Task<HttpAccessToken?> GetAsync(HttpAccessTokenContext context, CancellationToken cancellationToken)    {        // Request the WeChat server to obtain the Access Token        var weixinToken = await httpRemoteService.SendAsync(HttpRequestBuilder.Post("https://weixin.qq.com/login")            .WithoutTokenManagement(), cancellationToken);    // Skip Token management to avoid recursive calls (declarative requests use [SuppressTokenManagement])        return new HttpAccessToken(weixinToken.Token, DateTimeOffset.Parse(weixinToken.ExpiresAt));    }}

2. Enable automatic Access Token management for a specific HttpClient client:

cs
// Configure the default clientservices.AddHttpClient(string.Empty)    .ConfigureOptions((options, serviceProvider) =>    {        options.AccessTokenProvider = ActivatorUtilities.CreateInstance<WeiXinHttpAccessTokenProvider>(serviceProvider);    });// Configure a specific clientservices.AddHttpClient("weixin")    .ConfigureOptions((options, serviceProvider) =>    {        options.AccessTokenProvider = ActivatorUtilities.CreateInstance<WeiXinHttpAccessTokenProvider>(serviceProvider);    });

After completing the configuration above, all requests issued by that client will automatically manage the Access Token.

By default, the Access Token is sent in the form of an Authorization request header. Developers can specify the authentication scheme (such as Bearer) by setting the HttpAccessToken.Scheme property:

cs
public sealed class WeiXinHttpAccessTokenProvider(IHttpRemoteService httpRemoteService) : IHttpAccessTokenProvider{    /// <inheritdoc />    public async Task<HttpAccessToken?> GetAsync(HttpAccessTokenContext context, CancellationToken cancellationToken)    {        var weixinToken = await httpRemoteService.SendAsync(HttpRequestBuilder.Post("https://weixin.qq.com/login")            .WithoutTokenManagement(), cancellationToken);   // Skip Token management to avoid recursive calls (declarative requests use [SuppressTokenManagement])        return new HttpAccessToken(weixinToken.Token, DateTimeOffset.Parse(weixinToken.ExpiresAt))        {            Scheme = "Bearer"   // Specify the Bearer scheme        };    }}

If you need finer-grained control over how the Access Token is carried (such as placing it in a URL parameter or a Cookie), you can implement the IHttpAccessTokenConfigurator interface. It is recommended to implement this interface directly on the IHttpAccessTokenProvider implementation class, which both reduces type definitions and makes centralized management easier:

cs
public sealed class WeiXinHttpAccessTokenProvider(IHttpRemoteService httpRemoteService)    : IHttpAccessTokenProvider, IHttpAccessTokenConfigurator{    /// <inheritdoc />    public async Task<HttpAccessToken?> GetAsync(HttpAccessTokenContext context, CancellationToken cancellationToken)    {        var weixinToken = await httpRemoteService.SendAsync(HttpRequestBuilder.Post("https://weixin.qq.com/login")            .WithoutTokenManagement(), cancellationToken);   // Skip Token management to avoid recursive calls (declarative requests use [SuppressTokenManagement])        return new HttpAccessToken(weixinToken.Token, DateTimeOffset.Parse(weixinToken.ExpiresAt));    }    /// <inheritdoc />    public void Configure(HttpRequestBuilder httpRequestBuilder, HttpAccessToken httpAccessToken)    {        // Customize the Token injection method        httpRequestBuilder.AddBearerAuthentication(httpAccessToken.Value);   // Bearer authentication        // httpRequestBuilder.WithQueryParameter("access_token", httpAccessToken.Value);   // URL parameter        // httpRequestBuilder.WithCookie("access_token", httpAccessToken.Value);   // Cookie        // Other approaches...    }}

Of course, implementing IHttpAccessTokenConfigurator independently is also supported:

cs
public sealed class CustomHttpAccessTokenConfigurator : IHttpAccessTokenConfigurator{    /// <inheritdoc />    public void Configure(HttpRequestBuilder httpRequestBuilder, HttpAccessToken httpAccessToken)    {        // Put the Access Token into a custom request header (it is recommended to add replace: true)        httpRequestBuilder.WithHeader("X-Custom-Token", httpAccessToken.Value, replace: true);    }}

Then register the implementation in the service container:

cs
services.TryAddSingleton<IHttpAccessTokenConfigurator, CustomHttpAccessTokenConfigurator>();

Custom Access Token Refresh Trigger Conditions

By default, when an HTTP 401 Unauthorized response is received, the framework forcibly refreshes the Access Token and retries the request. If your API indicates that the Access Token is invalid through another status code (such as 403) or through the response content, you can override the ShouldRefreshAsync method of the IHttpAccessTokenProvider interface:

cs
public sealed class WeiXinHttpAccessTokenProvider(IHttpRemoteService httpRemoteService) : IHttpAccessTokenProvider{    /// <inheritdoc />    public async Task<HttpAccessToken?> GetAsync(HttpAccessTokenContext context, CancellationToken cancellationToken)    {        var weixinToken = await httpRemoteService.SendAsync(HttpRequestBuilder.Post("https://weixin.qq.com/login")            .WithoutTokenManagement(), cancellationToken);   // Skip Token management to avoid recursive calls (declarative requests use [SuppressTokenManagement])        return new HttpAccessToken(weixinToken.Token, DateTimeOffset.Parse(weixinToken.ExpiresAt));    }    /// <inheritdoc />    public async Task<bool> ShouldRefreshAsync(HttpAccessTokenContext context, HttpResponseMessage httpResponseMessage, CancellationToken cancellationToken)    {        // Example 1: Refresh when the status code is 401 or 403        // return httpResponseMessage.StatusCode == HttpStatusCode.Unauthorized        //        || httpResponseMessage.StatusCode == HttpStatusCode.Forbidden;        // Example 2: Parse the error code in the response content JSON        var content = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken);        return content?.Contains("\"errorCode\":\"TOKEN_EXPIRED\"") == true;    }}

When the method returns true, the Access Token is forcibly refreshed and the request is retried. Note that the retry is performed only once to avoid an infinite loop.

Passing Custom Data to IHttpAccessTokenProvider

If you need to pass dynamic parameters (such as username, password, etc.) when obtaining the Access Token, you can use the HttpRequestBuilder.WithAccessTokenData method. This data is automatically copied into HttpAccessTokenContext.Items for use by methods such as GetAsync.

cs
var result = await httpRemoteService.SendAsync(    HttpRequestBuilder.Get("https://api.furion.net/data")        .SetHttpClientName("myapi") // Optional        .WithAccessTokenData("username", "admin")        .WithAccessTokenData("password", "123456"));

Manually Setting the Access Token (SetAsync)

In addition to letting the framework automatically call GetAsync to obtain the Access Token, it also supports manually setting the Access Token after a successful login and then overriding RefreshAsync to implement refresh logic based on the RefreshToken.

By injecting the IHttpAccessTokenManager interface, call the SetAsync method to store the Token in the framework cache:

cs
// After a successful login, manually set the Access Tokenvar token = new HttpAccessToken(accessToken, expiresAt) { RefreshToken = refreshToken };await httpAccessTokenManager.SetAsync("myapi", token);

After that, the framework reads the Access Token from the cache and automatically calls your overridden RefreshAsync to refresh it when it expires. At this point GetAsync can return null or throw an exception (it will not be called).

A typical manual refresh implementation is as follows:

cs
public sealed class ManualTokenProvider : IHttpAccessTokenProvider, IHttpAccessTokenConfigurator{    public Task<HttpAccessToken?> GetAsync(HttpAccessTokenContext context, CancellationToken cancellationToken)        => Task.FromResult(HttpAccessToken.None);    public async Task<HttpAccessToken?> RefreshAsync(HttpAccessTokenContext context, HttpAccessToken? currentToken, CancellationToken cancellationToken)    {        // Use the RefreshToken in the current Token to obtain a new Token        var refreshToken = currentToken?.RefreshToken;        var response = await httpRemoteService.SendAsync(            HttpRequestBuilder.Post("https://auth.furion.net/refresh")                .WithHeader("X-Refresh-Token", refreshToken, replace: true)                .WithoutTokenManagement(), cancellationToken);   // Skip Token management to avoid recursive calls (declarative requests use [SuppressTokenManagement])        return new HttpAccessToken(response.Headers.GetValues("X-Access-Token").First(),            DateTimeOffset.UtcNow.AddHours(1)) { RefreshToken = response.Headers.GetValues("X-Refresh-Token").First() };    }    public void Configure(HttpRequestBuilder httpRequestBuilder, HttpAccessToken httpAccessToken)    {        // Put the Access Token into a custom request header (it is recommended to add replace: true)        httpRequestBuilder.WithHeader("Authorization", $"Bearer {httpAccessToken.Value}", replace: true);    }}

Built-in FurionAccessTokenProvider (Furion framework-specific)

If your server uses the JWT token mechanism of the Furion framework, you can directly use the built-in FurionAccessTokenProvider. This provider automatically handles the access-token and x-access-token response headers to implement seamless rolling refresh.

1. Register the provider

cs
services.AddHttpClient("furion_api")    .ConfigureOptions((options, serviceProvider) =>    {        options.AccessTokenProvider = ActivatorUtilities.CreateInstance<FurionAccessTokenProvider>(serviceProvider);    });

If you prefer to control instantiation manually, you can also pass dependencies explicitly:

cs
services.AddHttpClient("furion_api")    .ConfigureOptions((options, serviceProvider) =>    {        options.AccessTokenProvider = new FurionAccessTokenProvider(serviceProvider.GetRequiredService<IHttpAccessTokenManager>());    });

2. Manually set the initial Access Token after a successful login

cs
var token = new HttpAccessToken(initialAccessToken, expiresAt) { RefreshToken = initialRefreshToken };await httpAccessTokenManager.SetAsync("furion_api", token);

Afterwards, on every request, FurionAccessTokenProvider automatically carries Authorization: Bearer {token} and appends X-Authorization: Bearer {refresh_token} when the Access Token expires; the new Access Token returned by the server automatically updates the cache via the access-token and x-access-token response headers, with no additional code required.

Note: FurionAccessTokenProvider does not trigger a refresh based on HTTP 401, because its refresh logic is entirely driven by response headers. Be sure to call SetAsync to set the initial Access Token before first use.

Built-in WeChatAccessTokenProvider (WeChat Open Platform-specific)

If your project needs to call WeChat server-side APIs such as WeChat Official Accounts / Mini Programs, you can directly use the built-in WeChatAccessTokenProvider. This provider automatically manages the acquisition, caching, and refresh of access_token, and supports automatic retry based on WeChat error codes.

1. Register the provider

Obtaining the WeChat access_token requires appId and appSecret, which are passed in using ActivatorUtilities.CreateInstance:

cs
services.AddHttpClient("wechat_api")    .ConfigureOptions((options, serviceProvider) =>    {        options.AccessTokenProvider = ActivatorUtilities.CreateInstance<WeChatAccessTokenProvider>(            serviceProvider, "YourAppId", "YourAppSecret");    });

2. Automatic management flow https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-access-token/api_getaccesstoken.html

  • First request: The provider automatically calls the WeChat /cgi-bin/token endpoint to obtain access_token and caches it in memory (by default it expires 5 seconds early, to avoid using an invalid access_token due to network latency).
  • Automatic injection: The access_token is appended to the request URL as the query parameter ?access_token=xxx.
  • Expiry refresh: When the WeChat error codes 40001 (invalid credential), 40014 (invalid access_token), or 42001 (access_token expired) are received, the framework automatically obtains a new access_token and retries the request.
  • No manual action required: The entire lifecycle is managed automatically by the framework; there is no need to call SetAsync to manually set the initial access_token.

3. Error retry explanation

WeChatAccessTokenProvider overrides ShouldRefreshAsync and checks both the HTTP status code (401/403) and the errcode in the response JSON. Only error codes related to an invalid access_token trigger a refresh, avoiding meaningless retries caused by temporary network issues or a busy WeChat system (such as -1).


Multi-node Cluster Deployment

When the service is deployed in a multi-node cluster environment, the default in-memory cache causes each node to manage the Access Token independently. After one node obtains or refreshes the Token, the Token on other nodes becomes invalid, leading to repeated acquisition and refresh, and even triggering API rate limiting.

To solve this problem, you can migrate the storage of the Access Token from memory to a distributed cache (such as Redis). Simply implement the IHttpAccessTokenManager interface and replace the default service:

cs
public class RedisAccessTokenManager : IHttpAccessTokenManager{    // Implement the interface methods to store the Access Token in a distributed cache such as Redis}

Then replace the default implementation during service registration:

cs
services.Replace(ServiceDescriptor.Singleton<IHttpAccessTokenManager, RedisAccessTokenManager>());

After the replacement, all nodes share the same Access Token, completely avoiding Access Token conflicts and duplicate refresh problems between nodes.


With the above configuration, the framework automatically handles the acquisition, refresh, and injection of the Access Token, so developers do not need to worry about details such as the Access Token expiration time or invalid-token retries (for example, automatic resending on 401), significantly reducing the complexity of integrating with third-party APIs.