2.22Automatic Access Token Management
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 theAccess Tokenand its absolute expiration time (UTCtime).new(jwtToken): Passes in aJWT Tokenstring.
- Properties:
Value: TheAccess Tokenvalue (of typestring).ExpiresAt: The absolute expiration time of theAccess Token(of typeDateTimeOffset).Scheme: TheHTTPauthentication scheme (of typestring?).RefreshToken: The refresh token (of typestring?), internally providing convenient access based onItems["refresh_token"].Items: A shared data dictionary (of typeIDictionary<object, object?>) used to store custom data related to theAccess Token(such asrefresh_token, user identifiers, etc.).
- Static Properties:
None: Indicates that there is no availableAccess Token(of typeHttpAccessToken?).
- Methods:
IsExpired()checks whether theAccess Tokenhas expired.SetExpiresAt(expiresAt)sets the absolute expiration time of theAccess 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:
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:
// 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.
Configuring the Injection Location (Header, Query, Cookie, etc.)
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:
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:
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:
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:
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:
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.
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:
// 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:
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
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:
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
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:
FurionAccessTokenProviderdoes not trigger a refresh based onHTTP 401, because its refresh logic is entirely driven by response headers. Be sure to callSetAsyncto set the initialAccess Tokenbefore 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:
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/tokenendpoint to obtainaccess_tokenand caches it in memory (by default it expires5seconds early, to avoid using an invalidaccess_tokendue to network latency). - Automatic injection: The
access_tokenis appended to the requestURLas the query parameter?access_token=xxx. - Expiry refresh: When the WeChat error codes
40001(invalid credential),40014(invalidaccess_token), or42001(access_tokenexpired) are received, the framework automatically obtains a newaccess_tokenand retries the request. - No manual action required: The entire lifecycle is managed automatically by the framework; there is no need to call
SetAsyncto manually set the initialaccess_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:
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:
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.