I'm trying to secure communication between two services using OAuth (from now on referred to as consumer and provider).
Let's imagine that the consumer just started up. Now multiple http calls arrive to it almost simultaneously. The consumer needs to communicate with the provider in order to process the requests. I would very much like to have the consumer reuse a single token for this communication (instead of fetching a new token for each and every incoming request). First when the token expires, a new token should be fetched.
How to achieve this?
public class TokenProvider
{
private readonly HttpClient _httpClient;
private Token _token;
private object _lock = new object();
public TokenProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetTokenAsync()
{
if (_token != null && !_token.IsExpired())
{
return _token;
}
else
{
string oauthPostBody = string.Format(
"grant_type=client_credentials&client_id={0}&client_secret={1}", "fakeClientId", "fakeSecret");
var tokenEndpoint = ...;
var response = await _httpClient.PostAsync(tokenEndpoint.Uri, new StringContent(oauthPostBody));
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
lock (_lock)
{
if (_token == null || _token.IsExpired())
{
string expiresIn = jsonResponse.expires_in;
_token = new Token(jsonResponse.access_token, int.Parse(expiresIn));
}
return _token;
}
}
}
private class Token
{
private readonly string _token;
private readonly DateTime _expirationDateTime;
public Token(string token, int expiresIn)
{
_token = token;
_expirationDateTime = DateTime.UtcNow.AddSeconds(expiresIn);
}
public bool IsExpired()
{
return DateTime.UtcNow > _expirationDateTime;
}
public static implicit operator string(Token token)
{
return token._token;
}
}
}
However, I have my doubts that the above is the way to go. This suspicion is based on, among other things, compiler optimizations; see this post by Eric Lippert.
I'm trying to do such that the token can be read by many threads at once, but only updated by a single. I've also looked into ReaderWriterLockSlim, but this doesn't seem to help solve my problem. (Note that it gets even more complicated by the fact that I have an async call in GetTokenAsync.)
Update Based on @EricLippert remarks, I've updated the code:
public class TokenProvider
{
private readonly HttpClient _httpClient;
private readonly IApplicationConfig _config;
private Token _token;
private AsyncReaderWriterLock _lock = new AsyncReaderWriterLock();
public TokenProvider(HttpClient httpClient, IApplicationConfig config)
{
_httpClient = httpClient;
_config = config;
}
public bool TryGetExistingToken(out string token)
{
using (_lock.ReaderLock())
{
if (_token != null)
{
token = _token;
return true;
}
else
{
token = null;
return false;
}
}
}
public async Task<string> GetNewTokenAsync()
{
using (await _lock.WriterLockAsync())
{
if (_token != null && !_token.IsExpired())
{
return _token;
}
else
{
var clientId = _config.Get<string>("oauth.clientId");
var secret = _config.Get<string>("oauth.sharedSecret");
string oauthPostBody = string.Format(
"grant_type=client_credentials&client_id={0}&client_secret={1}", clientId, secret);
var queueEndpoint = _config.GetUri("recommendationQueue.host");
var tokenPath = _config.Get<string>("recommendationQueue.path.token");
var tokenEndpoint = new UriBuilder(queueEndpoint) {Path = tokenPath};
var response = await _httpClient.PostAsync(tokenEndpoint.Uri, new StringContent(oauthPostBody));
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (_token == null || _token.IsExpired())
{
string expiresIn = jsonResponse.expires_in;
string accessToken = jsonResponse.access_token;
_token = new Token(accessToken, int.Parse(expiresIn));
}
return _token;
}
}
}
private class Token
{
private readonly string _token;
private readonly DateTime _expirationDateTime;
public Token(string token, int expiresIn)
{
_token = token;
_expirationDateTime = DateTime.UtcNow.AddSeconds(expiresIn);
}
public bool IsExpired()
{
return DateTime.UtcNow > _expirationDateTime;
}
public static implicit operator string(Token token)
{
return token._token;
}
}
}
I'm using this AsyncReaderWriterLock by Stephen Cleary. Is this a better approach? Or have I just digged myself into an even larger hole?