1

I don't know how to implement the client part of the Client Credential Flow for two APIs.

Scenario: An API A obtains a JWT token (your own token) from an identity server (keycloak) to access an API B.

Technology: .NET Core with C #

How to store the token in API A to be used in requests for API B? How to manage token expiration so that it is refreshed?

Is it a good option to use a singleton instance of a class that stores the token and uses a Timer to refresh the token? Is it better to have a worker service (background) to manage token expiration?

Are there other ways to store and manage token expiration in this scenario?

Is possible to use IdentityModel with keycloak?

7
  • does API A only have one token or one per user? Commented Sep 10, 2020 at 6:50
  • The API A have only one token. Commented Sep 10, 2020 at 15:09
  • Yes, you can store the token in a singleton. Just take care of thread safety. Regarding your API calls, use services.AddHttpClient<YourAClient>((serviceProvider, httpClient) => {}) Commented Sep 11, 2020 at 10:26
  • And how to do for manage expiration? A timer to refresh the token before expiration is good idea? Commented Sep 12, 2020 at 0:50
  • I don't see any official concept for this. You would have to have a reused code that checks if your response is 401. If 401, you can request a refresh token and set header Commented Sep 13, 2020 at 10:43

1 Answer 1

1

You can use a singleton similar to this:

public class TokenStore { public string Token { get; set; } public string RefreshToken { get; set; } } 

Next, you can create a System.Net.Http.DelegatingHandler to take care of the whole Token process. It would be something like this:

public class MyTokenHandler : DelegatingHandler { private readonly TokenStore _tokenStore; private TaskCompletionSource<object> updateTokenTaskCompletionSource; public MyTokenHandler(TokenStore tokenStore) { _tokenStore = tokenStore; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // check if token is already being fetched if (updateTokenTaskCompletionSource != null) await updateTokenTaskCompletionSource.Task; var httpResponse = await InternalSendAsync(request, cancellationToken); if (httpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized) // you can add conditions such as excluding Paths and checking response message to avoid recursion // you can also verify token expiary based on time { // intentionally not passing in the refresh token // at this point we know there is an expired token. So, we can update token regardless of the main request being cancelled await UpdateTokenAsync(); httpResponse = await InternalSendAsync(request, cancellationToken); } return httpResponse; } private async Task UpdateTokenAsync(CancellationToken cancellationToken = default) { // taskCompletionSource handles multiple requests attempting to refresh token at the same time if (updateTokenTaskCompletionSource is null) { updateTokenTaskCompletionSource = new TaskCompletionSource<object>(); try { var refreshRequest = new HttpRequestMessage(HttpMethod.Post, "/token"); var refreshResponse = await base.SendAsync(refreshRequest, cancellationToken); _tokenStore.Token = "updated token here"; } catch (Exception e) { updateTokenTaskCompletionSource.TrySetException(e); updateTokenTaskCompletionSource = null; throw new Exception("Failed fetching token", e); } updateTokenTaskCompletionSource.TrySetResult(null); updateTokenTaskCompletionSource = null; } else await updateTokenTaskCompletionSource.Task; } private async Task<HttpResponseMessage> InternalSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenStore.Token); return await base.SendAsync(request, cancellationToken); } } 

Finally, in your startup.cs you can add this code:

services.AddHttpClient<MyClientService>(httpClient => { httpClient.BaseAddress = new Uri("Your base URL"); }).AddHttpMessageHandler<MyTokenHandler>(); 

Note that I haven't accounted for setting the RefreshToken. You can set it based on the requirement. Here is the whole sample project: https://github.com/neville-nazerane/httpclient-refresh/

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! I'm going to do some tests. :-)
Do you know if it is possible to use IdentityModel with Keycloak?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.