0

In the below code, am looking to renew access token if it is expired. But nothing works. I tried to debug with breakpoint on line return response but that breakpoint will not trigger.

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpResponseMessage response = null;
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
            response = await base.SendAsync(request, cancellationToken);

            if (response.StatusCode != HttpStatusCode.Unauthorized)
                return response;

            var tokenResponse = _tokenGenerator.GetAccessToken(accessTokenInfo).Result;
            if (tokenResponse != null)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse);
                response = await base.SendAsync(request, cancellationToken);
            }

            return response;
        }
rajesh
  • 1,475
  • 10
  • 23
  • the usual way to renew a token when expired is to use expiration data, coming from the token endpoint with the token response. you just cache the token for that interval and each time you need to set the bearer you check if it's still in the cache. when not, request a fresh one. and so on and so on. – d_f Jun 27 '19 at 10:01
  • any sample code will be good to move forward? – rajesh Jun 27 '19 at 11:14
  • @RuardvanElburg I need to do it with client credentials flow. So I dont need refresh token. I will done it with client id and secret. But my actual issue is not solved which am asked as question – rajesh Jun 27 '19 at 11:49
  • @d_f its web hosted api and its works right now. I forgot to change the port to localhost. But anyway if my token is expired, I got response status as Unauthorized, So Now I want to know the actual reason for the Unauthorized error. How to get it? – rajesh Jun 27 '19 at 14:05
  • once again: it's better to precheck, than to handle an error afterwards. so just persist something like `_accessTokenValidity = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn);` and check every time before any request. when `DateTime.UtcNow` exceeds the value you keep, request a fresh token (and update the `_accessTokenValidity`) – d_f Jun 27 '19 at 14:20
  • updated the answer, by adding essentials from my comments above + the code is absolutely working now as it is. – d_f Jun 28 '19 at 13:59

1 Answers1

1

The usual way to renew a token when expired is to use expiration data, coming from the token endpoint with the token response. You can cache the token for that interval and each time you need to set the bearer you first try to get it from the cache. When the token expires, the cache returns null so you request a fresh token and cache it again.
Please see the example based on the article by Dominick Baier. You will need to install the IdentityModel nuget package if not yet done.

public class TokenClientOptions
{
    public string Address { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

public class TokenClient 
{
    private const string AccessTokenCacheKey = "access_token";

    public HttpClient Client { get; }
    public TokenClientOptions Options { get; }
    public ILogger<TokenClient> Logger { get; }
    public IDistributedCache Cache { get; }


    public TokenClient(HttpClient client, IOptions<TokenClientOptions> options,
            IDistributedCache cache,
            ILogger<TokenClient> logger)
    {
         Client = client;
         Options = options.Value;
         Cache = cache;
         Logger = logger;
    }


    public async Task<string> GetToken()
    {
         var token = Cache.GetString(AccessTokenCacheKey);
         if (token != null)
                return token;

         var response = await Client.
             RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
         {
                Address = Options.Address,
                ClientId = Options.ClientId,
                ClientSecret = Options.ClientSecret
         });

         Cache.SetString(AccessTokenCacheKey, response.AccessToken,
                new DistributedCacheEntryOptions()
                    {AbsoluteExpirationRelativeToNow = 
                           TimeSpan.FromSeconds(response.ExpiresIn)});
         return response.AccessToken;
     }
}

public static class Extensions
{
    public static void AddTokenClient(this IServiceCollection services) {
        services.Configure<TokenClientOptions>(options =>
        {
             options.Address = "https://demo.identityserver.io/connect/token";
             options.ClientId = "client";
             options.ClientSecret = "secret";
        });

        services.AddDistributedMemoryCache();
        services.AddHttpClient<TokenClient>();
    }
}

then in your Startup.ConfigureServices you add: services.AddTokenClient();

and after that you can inject TokenClient into your API controllers and use it like you did in your sample above:

public class TestController : Controller
{
    public TokenClient TokenClient { get; }

    public TestController(TokenClient tokenClient) => TokenClient = tokenClient;

    public async Task<HttpResponseMessage> Index()
    {
        var request = new HttpRequestMessage(
            HttpMethod.Get, "https://demo.identityserver.io/api/test");
        var accessToken = await TokenClient.GetToken();
        request.SetBearerToken(accessToken);
        var client = HttpClientFactory.Create();
        var response = await client.SendAsync(request, new CancellationToken());
        return response;
    }
}
d_f
  • 4,599
  • 2
  • 23
  • 34