private RetryParams ComputeAdalRetry(Exception ex) { if (ex is AdalServiceException) { AdalServiceException adalServiceException = (AdalServiceException)ex; // When the Service Token Server (STS) is too busy because of “too many requests”, // it returns an HTTP error 429 with a hint about when you can try again (Retry-After response field) as a delay in seconds if (adalServiceException.ErrorCode == msalTemporarilyUnavailable || adalServiceException.StatusCode == 429) { RetryConditionHeaderValue retryAfter = adalServiceException.Headers.RetryAfter; // Depending on the service, the recommended retry time may be in retryAfter.Delta or retryAfter.Date. Check both. if (retryAfter != null && retryAfter.Delta.HasValue) { return(new RetryParams(retryAfter.Delta.Value)); } else if (retryAfter != null && retryAfter.Date.HasValue) { return(new RetryParams(retryAfter.Date.Value.Offset)); } // We got a 429 but didn't get a specific back-off time. Use the default return(RetryParams.DefaultBackOff(0)); } } return(RetryParams.DefaultBackOff(0)); }
private static RetryParams HandleAdalException(Exception ex, int currentRetryCount) { if (IsAdalServiceUnavailable(ex)) { return(ComputeAdalRetry(ex)); } else if (ex is ThrottleException) { // This is an exception that we threw, with knowledge that // one of our threads is trying to acquire a token from the server // Use the retry parameters recommended in the exception ThrottleException throttlException = (ThrottleException)ex; return(throttlException.RetryParams ?? RetryParams.DefaultBackOff(currentRetryCount)); } else if (IsAdalServiceInvalidRequest(ex)) { return(RetryParams.StopRetrying); } else { // We end up here is the exception is not an ADAL exception. An example, is under high traffic // where we could have a timeout waiting to acquire a token, waiting on the semaphore. // If we hit a timeout, we want to retry a reasonable number of times. return(RetryParams.DefaultBackOff(currentRetryCount)); } }
private RetryParams HandleTokenProviderException(Exception e, int retryCount) { _logger.LogError(e, "Exception when trying to acquire token using MSI!"); return(e is AzureServiceTokenProviderException // BadRequest ? RetryParams.StopRetrying : RetryParams.DefaultBackOff(retryCount)); }
private RetryParams HandleMsalException(Exception ex, int ct) { _logger?.LogError(ex, "Exception acquiring token through MSAL."); if (ex is MsalServiceException msalException) { _logger?.LogWarning(msalException, $"MSAL service error code: {msalException.ErrorCode}."); // Service error with status code "temporarily_unavailable" is retryable. // Spec and reference: https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes. if (msalException.ErrorCode == "temporarily_unavailable") { return(RetryParams.DefaultBackOff(ct)); } } return(RetryParams.StopRetrying); }
private async Task <AuthenticatorResult> AcquireTokenAsync(bool forceRefresh = false) { if (_clientApplication == null) { throw new InvalidOperationException("AcquireTokenAsync should not be called for empty credentials."); } bool acquired = false; try { // Limiting concurrency on MSAL token acquisitions. When the Token is in cache there is never // contention on this semaphore, but when tokens expire there is some. However, after measuring performance // with and without the semaphore (and different configs for the semaphore), not limiting concurrency actually // results in higher response times, more throttling and more contention. // Without the use of this semaphore calls to AcquireTokenAsync can take tens of seconds under high concurrency scenarios. #pragma warning disable VSTHRD103 // Call async methods when in an async method acquired = tokenRefreshSemaphore.Wait(SemaphoreTimeout); #pragma warning restore VSTHRD103 // Call async methods when in an async method // If we are allowed to enter the semaphore, acquire the token. if (acquired) { // Note that in MSAL, we dont pass resources anymore, and we instead pass scopes. To be recognized by MSAL, we append the '/.default' to the scope. // Scope requirements described in MSAL migration spec: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-migration. const string scopePostFix = "/.default"; var scope = _scope ?? OAuthScope; if (!scope.EndsWith(scopePostFix, StringComparison.OrdinalIgnoreCase)) { scope = $"{scope}{scopePostFix}"; } // Acquire token async using MSAL.NET // This will use the cache from the application cache of the MSAL library, no external caching is needed. var msalResult = await _clientApplication .AcquireTokenForClient(new[] { scope }) .WithAuthority(_authority ?? OAuthEndpoint, _validateAuthority) .WithForceRefresh(forceRefresh) .ExecuteAsync().ConfigureAwait(false); // This means we acquired a valid token successfully. We can make our retry policy null. return(new AuthenticatorResult() { AccessToken = msalResult.AccessToken, ExpiresOn = msalResult.ExpiresOn }); } else { // If the token is taken, it means that one thread is trying to acquire a token from the server. // Throttle this request to allow the currently running request to fulfill and then let this one get the result from the cache. throw new ThrottleException() { RetryParams = RetryParams.DefaultBackOff(0) }; } } finally { // Always release the semaphore if we acquired it. if (acquired) { ReleaseSemaphore(); } } }