auth0/auth0.net

AuthenticationApiClient caches JsonWebKeySet only for the first 10 minutes for GetTokenAsync(RefreshTokenRequest request)

nmuhundanwow opened this issue · 2 comments

Checklist

  • I have looked into the Readme and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Description

Expection

When a single instance of AuthenticationApiClient is created and GetTokenAsync(RefreshTokenRequest request, ....) is called multiple times, the expectation is https://metro60-uat.woolworths-dev.auth0app.com/.well-known/jwks.json is only called every 10 minutes as per the library and cached JsonWebKeySet is used other times.

Actual

The caching of JsonWebKeySet in AuthenticationApiClient works as expected for the first 10 minutes when GetTokenAsync(RefreshTokenRequest request, ....) is called multiple times. However after 10 minutes are elapsed, any number of subsequent calls to GetTokenAsync(RefreshTokenRequest request, ....) will always lead AuthenticationApiClient calling https://metro60-uat.woolworths-dev.auth0app.com/.well-known/openid-configuration and https://metro60-uat.woolworths-dev.auth0app.com/.well-known/jwks.json to retrieve JsonWebKeySet but never caches it

Solution

The problem is in this line of code
cache.TryAdd(key, new Entry { Task = task, CachedAt = now }); will not replace the old cache entry and this check will always return false after first 10 minutes.
One such solution is to replace that line to
cache[key] = new Entry { Task = task, CachedAt = now };

Reproduction

This is the code to demonstrate the behavior

public class Auth0AuthenticationApiClientHandler: HttpClientHandler
{
    private readonly ILogger<Auth0AuthenticationApiClientHandler> _logger;

    public Auth0AuthenticationApiClientHandler(ILogger<Auth0AuthenticationApiClientHandler> logger)
    {
        _logger = logger;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        _logger.LogInformation("{Time}: Calling {Method} {Url}", DateTimeOffset.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture), request.Method, request.RequestUri);
        return base.SendAsync(request, cancellationToken);
    }
}

...
...
...

_auth0OtpClient= AuthenticationApiClient("Auth0 Host URL",
            new HttpClientAuthenticationConnection(auth0AuthenticationApiClientHandler));

...
...
...
_logger.LogInformation("{Time}: Start of getting new refresh token", DateTimeOffset.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture));

var refreshTokenResponse = await _auth0OtpClient.GetTokenAsync(refreshOtpTokenRequest, cancellationToken);
_logger.LogInformation("{Time}: End of getting new refresh token", DateTimeOffset.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture));            

And logs were

      2023-12-21T07:17:20: Start of getting new refresh token
      2023-12-21T07:17:20: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:17:21: Calling GET https://{{DOMAIN}}/.well-known/openid-configuration
      2023-12-21T07:17:21: Calling GET https://{{DOMAIN}}/.well-known/jwks.json
      2023-12-21T07:17:21: End of getting new refresh token

      2023-12-21T07:17:59: Start of getting new refresh token
      2023-12-21T07:17:59: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:17:59: End of getting new refresh token

      2023-12-21T07:18:21: Start of getting new refresh token
      2023-12-21T07:18:21: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:18:22: End of getting new refresh token

      2023-12-21T07:26:22: Start of getting new refresh token
      2023-12-21T07:26:22: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:26:22: End of getting new refresh token

      2023-12-21T07:27:59: Start of getting new refresh token
      2023-12-21T07:27:59: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:28:00: Calling GET https://{{DOMAIN}}/.well-known/openid-configuration
      2023-12-21T07:28:00: Calling GET https://{{DOMAIN}}/.well-known/jwks.json
      2023-12-21T07:28:00: End of getting new refresh token

      2023-12-21T07:28:14: Start of getting new refresh token
      2023-12-21T07:28:14: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:28:14: Calling GET https://{{DOMAIN}}/.well-known/openid-configuration
      2023-12-21T07:28:14: Calling GET https://{{DOMAIN}}/.well-known/jwks.json
      2023-12-21T07:28:14: End of getting new refresh token

      2023-12-21T07:28:21: Start of getting new refresh token
      2023-12-21T07:28:21: Calling POST https://{{DOMAIN}}/oauth/token
      2023-12-21T07:28:22: Calling GET https://{{DOMAIN}}/.well-known/openid-configuration
      2023-12-21T07:28:22: Calling GET https://{{DOMAIN}}/.well-known/jwks.json
      2023-12-21T07:28:22: End of getting new refresh token

As it can be observed, the first call to GetTokenAsync was made on 2023-12-21T07:17:20 and OIDC endpoints called for the first time and they were never called in the next 10 minutes for subsequent calls of GetTokenAsync, until 2023-12-21T07:26:22. However after 2023-12-21T07:27:59, every calls to GetTokenAsync lead AuthenticationApiClient to call OIDC endpoints every time.

Additional context

No response

auth0.net version

7.25.1

.NET version

8.0

Thanks!

I opened a PR to fix this, can you take a look and see if this would solve your use-case as well?

Thanks!

I opened a PR to fix this, can you take a look and see if this would solve your use-case as well?

Yes, the PR will fix it. Thanks for the quick PR.