0

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?

SabrinaMH
  • 221
  • 1
  • 3
  • 11
  • I am finding it somewhat bizarre that you can have 20 different token providers each with their own client, and they can all return the same token. Does that not strike you as deeply broken? Is it not the case that the token provider should provide a token *from the given client* ??? – Eric Lippert May 30 '17 at 20:12
  • You are absolutely right! I've changed the code such that _token and _lock are now instance fields. – SabrinaMH May 31 '17 at 06:28
  • And then I make sure, when bootstrapping the application, that I only ever have a single instance of TokenProvider. – SabrinaMH May 31 '17 at 08:34
  • What I don't understand is what is *guaranteed* by any of these methods. Plainly it is not that a valid token is returned. The best you can say is that a good effort will be made to return a valid token. How is the token used? Do you sit there in a loop trying to obtain a valid token and re-trying if it turns out to be invalid between the time of check and the time of use? – Eric Lippert May 31 '17 at 13:44
  • Also, consider the following scenario. Thread A takes lock X. We switch to thread B. Thread B takes the writer lock and then awaits while holding the lock. So we return to the caller which attempts to do other work. That work causes thread B to attempt to take lock X, so B blocks and we switch back to thread A. Now thread A attempts to take the writer lock and blocks. Both threads are now blocked and the locks will never be released. What prevents this scenario in your program? – Eric Lippert May 31 '17 at 13:48
  • I am painfully aware that the current public API/interface of TokenProvider leaves a huge burden on the caller wrt. avoid deadlocks (this should really be the responsibility of TokenProvider, because the caller should never need to know internal details about another class) - and also the API is very unintuitive. However, I have a hard time coming up with a better approach. Any hints would be much appreciated! This will be used in a web app, which receives many, many calls and therefore I would like to allow multiple threads to read the token at the same time. – SabrinaMH May 31 '17 at 14:06
  • So you realize you're building a "candy machine interface". (http://www.tetzfiles.com/eric/candyMachineInterfaces.html). You're getting hung up on the mechanisms of how to obtain the token. Approach the design problem from the other direction. **How does the user of your API want to use the abstraction of consumer-talks-to-producer?** I'd be willing to bet as much as a dollar that the word "token" is nowhere in that description. What is the *abstraction* that you are representing? What are the signatures of its methods? Start there. – Eric Lippert May 31 '17 at 14:10
  • But eventually it has to get down to the specifics, right? Service A might call Service B 100 times almost simultaneously (it's entirely service-to-service comm; no actual user). For it to call Service B, it needs to obtain a token and present to Service B. I think it should be Service A's responsibility to only fetch one token and reuse it until it expires. And that's the part I have a really hard time getting right. (I understand what you're saying wrt. looking at the bigger picture, but I seem to wind up with this technical problem no matter my perspective). Thanks for all your effort! – SabrinaMH May 31 '17 at 14:19
  • Sure, at some point the problem has to be solved. Concentrate on solving the problem that actually has to be solved. Plainly the fundamental problem is *a token can be expired* and therefore *any operation that uses a token can fail*, and therefore *any operation that uses a token has to either throw or automatically retry with a new token*. Solve that *fundamental* problem first, because it isn't going away. Your deadlockable algorithm is your own fault; you created that problem. – Eric Lippert May 31 '17 at 14:25
  • But the thing is, that I already solved that problem. And then I found out that when I receive many calls simultaneously, then it's pretty random how many new tokens I fetch. And that's the reason I began digging this deep hole; to make sure that I only ever fetch one. – SabrinaMH May 31 '17 at 14:36
  • Do you believe that I haven't found the right abstraction; and in doing so my main problem (that led to my deadloackable algorithm) goes away? – SabrinaMH May 31 '17 at 16:23

1 Answers1

2

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

We don't have to suppose exotic compiler optimizations. First off, there's the obvious problem that whether a token is expired changes:

  • There is an unexpired token in the static variable.
  • Thread A detect that there is an unexpired token and enters the if.
  • Thread A suspends.
  • Thread B runs for long enough that the token expires.
  • Thread A resumes and returns an expired token.

So there's that. We have no guarantee that the token returned is valid. But it's far worse than that. We don't even have a guarantee that the token that is returned is the current token.

  • There is an unexpired token in the static variable.
  • Thread A detect that there is an unexpired token and enters the if.
  • Thread A puts the unexpired token on the evaluation stack for the return.
  • Thread A suspends.
  • Thread B runs for long enough that the token expires.
  • Thread C runs, detects that the token is expired, and replaces it with a different token.
  • Thread A resumes and returns an expired token that is not even the current contents of the variable.

You have a TOCTOU problem here, irrespective of any issues with your implementation of double-checked locking. That is Time-Of-Check-is-not-Time-Of-Use. All you know about the token is that it was not expired at some time in the past, but that is true of all tokens.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks so much for the very helpful remarks - and for making me aware of the formal name of the problem that I'm facing. I'm puzzled that I cannot seem to find anyone describing how to deal with the concrete problem (I guess many would want to reuse a token for service to service communication). Do you by any chance know of any resources? – SabrinaMH May 31 '17 at 06:32