internal async Task<string> GetAccessTokenForRequestAsync(CancellationToken cancellationToken) { Task<TokenResponse> refreshTask; lock (_lock) { // If current token is not soft-expired, then return it. if (_token != null && !_token.IsExpired(_clock)) { return _token.AccessToken; } // Token refresh required, so start a task if not already started if (_refreshTask == null) { // Task.Run is required if the refresh completes synchronously, // otherwise _refreshTask is updated in an incorrect order. // And Task.Run also means it can be run here in the lock. _refreshTask = Task.Run(RefreshTokenAsync); // Let's make sure that exceptions in _refreshTask are always observed. // Note that we don't keep a reference to this new task as we don't really // care about the errors, and we want calling code explicitly awaiting on _refreshTask // to actually fail if there's an error. We just schedule it to run and that's enough for // avoiding exception observavility issues. _refreshTask.ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted); } // If current token is not hard-expired, then return it. if (_token != null && !_token.IsEffectivelyExpired(_clock)) { return _token.AccessToken; } refreshTask = _refreshTask; async Task LogException(Task task) { try { await task.ConfigureAwait(false); } catch (Exception ex) { _logger.Debug($"An error occured on a background token refresh task.{Environment.NewLine}{ex}"); } } } refreshTask = refreshTask.WithCancellationToken(cancellationToken); return (await refreshTask.ConfigureAwait(false)).AccessToken; }
public void Expiry( DateTime now, DateTime issuedAt, long?expiresInSeconds, string accessToken, string idToken, bool expectedExpired, bool expectedEffectiveExpires) { var clock = new MockClock(now); var token = new TokenResponse { IssuedUtc = issuedAt, ExpiresInSeconds = expiresInSeconds, AccessToken = accessToken, IdToken = idToken }; Assert.Equal(expectedExpired, token.IsExpired(clock)); Assert.Equal(expectedEffectiveExpires, token.IsEffectivelyExpired(clock)); }
internal async Task <string> GetAccessTokenForRequestAsync(CancellationToken cancellationToken) { Task <TokenResponse> refreshTask; lock (_lock) { // If current token is not soft-expired, then return it. if (_token != null && !_token.IsExpired(_clock)) { return(_token.AccessToken); } // Token refresh required, so start a task if not already started if (_refreshTask == null) { // Task.Run is required if the refresh completes synchronously, // otherwise _refreshTask is updated in an incorrect order. // And Task.Run also means it can be run here in the lock. _refreshTask = Task.Run(RefreshTokenAsync); } // If current token is not hard-expired, then return it. if (_token != null && !_token.IsEffectivelyExpired(_clock)) { return(_token.AccessToken); } refreshTask = _refreshTask; } // Otherwise block on refresh task. if (cancellationToken.CanBeCanceled) { // Reasonably simple way of creating a task that can be cancelled, based on another task. // (It would be nice if this were simpler.) refreshTask = refreshTask.ContinueWith( task => ResultWithUnwrappedExceptions(task), cancellationToken, TaskContinuationOptions.None, TaskScheduler.Default); } return((await refreshTask.ConfigureAwait(false)).AccessToken); }