I have a Web API and for some requests we are sending requests to a third party API that requires request authentication. It's a simple bearer token, which I'd like to reuse across requests, because the authentication process can take up to two seconds roughly, and obviously I don't want to flood the external service. The token is short-lived, 5 minutes only, so every 5 minutes I have to obtain a new one (unless no-one is requesting it). And during that obtaining process, I'd like to lock the process, so threads handling other requests coming in would have to wait for the first one to finish the job. My API can be quite under load, so during obtaining process (2 seconds), there could be up to a dozen or more requests coming in.
I use memory cache to store the token and share it across application. I do the double check on memory cache to filter out requests that would get into the first if statement, but would be waiting for the first request to finish obtaining a new token. That way, those requests will not trigger the token obtaining process once the wait is over, because the first request already did it.
Please take a look and tell me what do you think? Any risks, code smells? Thanks!
public class ApiTokenProvider
{
private const string TokenCacheKey = "__TOKEN_CACHE_KEY";
private static readonly SemaphoreSlim AuthorizationTokenSemaphore = new SemaphoreSlim(1, 1);
private readonly IMemoryCache memoryCache;
public ApiTokenProvider(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
public async Task<string> GetToken(CancellationToken cancellationToken)
{
if (!memoryCache.TryGetValue<string>(TokenCacheKey, out var token))
{
try
{
await AuthorizationTokenSemaphore.WaitAsync(cancellationToken);
if (!memoryCache.TryGetValue(TokenCacheKey, out token))
{
return await CallThirdPartyApiForAuthToken(cancellationToken);
}
}
finally
{
AuthorizationTokenSemaphore.Release();
}
}
return token;
}
private async Task<string> CallThirdPartyApiForAuthToken(CancellationToken cancellationToken)
{
// Calling a third party using HttpClient. Actual code removed for brevity.
await Task.Delay(2000, cancellationToken);
var token = "TOKEN";
var expireDate = DateTimeOffset.UtcNow.AddMinutes(5);
memoryCache.Set(TokenCacheKey, token, expireDate);
return token;
}
}
MemoryCache
then there is no need forSemaphoreSlim
asMemoryCache
is thread-safe. You could check its implementation to verify that. However, if you're expecting a custom implementation that you're afraid is not a thread-safe, then your approach would be feasible. \$\endgroup\$MemoryCache
is thread-safe. I'm more concerned about thread-safety of the token obtaining process, so other threads do not interfere meanwhile. \$\endgroup\$