I am using IHttpClientFactory for sending requests and receiving HTTP responses from two external APIs using Net Core 2.2.
I am looking for a good strategy to get a new access token using a refresh token that has been stored in the appsettings.json. The new access token needs to be requested when the current request returns 403 or 401 errors, When the new access and refresh token have been obtained, the appsettings.json needs to be updated with the new values in order to be used in subsequent requests.
I am using two clients to send requests to two different APIs but only one of them use token authentication mechanism.
I have implemented something simple that works but i am looking for a more elegant solution that can update the header dynamically when the current token has expired :
I have registered the IHttpClientFactory in the Startup.ConfigureServices method as follows:
services.AddHttpClient();
Once registered i am using it in two different methods to call two different APIs, the first method is:
public async Task<AirCallRequest> GetInformationAsync(AirCallModel model) { try { CandidateResults modelCandidateResult = null; var request = new HttpRequestMessage(HttpMethod.Get, "https://*******/v2/*****"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _appSettings.Value.Token); var clientJAAPI = _httpClientFactory.CreateClient(); var responseclientJAAPI = await clientJAAPI.SendAsync(request); if (responseclientJAAPI.IsSuccessStatusCode) { modelCandidateResult = await responseclientJAAPI.Content .ReadAsAsync<CandidateResults>(); .... } if ((responseclientJAAPI .StatusCode.ToString() == "Unauthorized") { await RefreshAccessToken(); //Calls recursively this method again return await GetInformationAsync(model); } return null; } catch (Exception e) { return null; } }
The refresh Token method looks like that:
private async Task RefreshAccessToken() { var valuesRequest = new List<KeyValuePair<string, string>>(); valuesRequest.Add(new KeyValuePair<string, string>("client_id", "*****")); valuesRequest.Add(new KeyValuePair<string, string>("client_secret","****")); valuesRequest.Add(new KeyValuePair<string, string>("grant_type", "refresh_token")); valuesRequest.Add(new KeyValuePair<string, string>("refresh_token", "*****")); RefreshTokenResponse refreshTokenResponse = null; var request = new HttpRequestMessage(HttpMethod.Post, "https://*****/connect/token"); request.Content = new FormUrlEncodedContent(valuesRequest); var clientJAAPI = _httpClientFactory.CreateClient(); var responseclientJAAPI = await clientJAAPI.SendAsync(request); if (responseclientJAAPI.IsSuccessStatusCode) { refreshTokenResponse = await responseclientJAAPI.Content.ReadAsAsync<RefreshTokenResponse>(); //this updates the POCO object representing the configuration but not the appsettings.json : _appSettings.Value.Token = refreshTokenResponse.access_token; } }
Notice that I am updating the POCO object representing the configuration but not the appsettings.json, so the new values are stored in memory. I want to update the appsettings.json for subsequent requests.
If the solution proposed require to define the main settings for the Httpclient in the Startup.ConfigureService, it needs to allow to create different instances of the HttpClien, because one of the HttpClient instances (use in another method to call a second API) doesn't require a token to send the requests.
Using a Refresh Token These client credentials and the refresh_token can be used to create a new value for the access_token . To refresh the access token, select the Refresh access token API call within the Authorization folder of the Postman collection. Next, click the Send button to request a new access_token .
Once they expire, client applications can use a refresh token to "refresh" the access token. That is, a refresh token is a credential artifact that lets a client application get new access tokens without having to ask the user to log in again.
Request an Updated Access Token. A connected app can use the refresh token to get a new access token by sending one of the following refresh token POST requests to the Salesforce token endpoint. The connected app can send the client_id and client_secret in the body of the refresh token POST request, as shown here.
Looks like you need DelegatingHandler. In two words you can "intercept" your http request and add the Authorization header, then try to execute it and if token was not valid, refresh token and retry one more time. Something like:
public class AuthenticationDelegatingHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken); var response = await base.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden) { token = await RefreshTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken); response = await base.SendAsync(request, cancellationToken); } return response; } }
You register this delegating handler in Startup.cs like that:
services.AddTransient<AuthenticationDelegatingHandler>(); services.AddHttpClient("MySecuredClient", client => { client.BaseAddress = new Uri("https://baseUrl.com/"); }) .AddHttpMessageHandler<AuthenticationDelegatingHandler>();
And use like that:
var securedClient = _httpClientFactory.CreateClient("MySecuredClient"); securedClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "v2/relativeUrl"));
Regarding storing refresh token in appsetting.json. I don't think it's a good idea as refresh token doesn't have expiration time. If you can use credentials to obtain new token for the first time, use it, and then store refresh token in-memory for further refreshes.
Here you can see how I manage client credential token refreshes and try to make it work for your scenario.
Update:
Here you can find same idea but implemented by professionals and available in nuget. The usage is very simple:
services.AddAccessTokenManagement(options => { options.Client.Clients.Add("identityserver", new ClientCredentialsTokenRequest { Address = "https://demo.identityserver.io/connect/token", ClientId = "m2m.short", ClientSecret = "secret", Scope = "api" // optional }); }); services.AddHttpClient<MyClient>(client => { client.BaseAddress = new Uri("https://demo.identityserver.io/api/"); }) .AddClientAccessTokenHandler();
Requests sent by MyClient
will always have valid bearer token. The refresh performed automatically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With