-
Notifications
You must be signed in to change notification settings - Fork 367
Retry Policy
This document explains how to implement a custom retry policy around token acquisition operations. For other tips on how to increase the availability of your service, see https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/High-availability
MSAL.NET implements a simple retry-once mechanism for errors with HTTP error codes 500-600. For more control / availability, consider disabling the default retry policy and define your own.
Note that (AAD) may return a Retry-After header indicating to clients to pause for a few seconds, which needs to be taken into account.
Note: You must use MSAL 4.47.2 or higher.
public async Task<AuthenticationResult> GetTokenAsync()
{
var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithHttpClientFactory(
// consider using a higly scalable HttpClient, the default one is not great.
// See https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use
httpClientFactory: null,
// Disable MSAL's internal simple retry policy
retryOnceOn5xx: false)
.Build();
// For caching see https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet#in-memory-token-cache-1
app.AddInMemoryCache();
AsyncRetryPolicy retryPolicy = GetMsalRetryPolicy();
AuthenticationResult result = await retryPolicy.ExecuteAsync(
() => app.AcquireTokenForClient(TestConstants.s_scope.ToArray()).ExecuteAsync())
.ConfigureAwait(false);
return result;
}
private static AsyncRetryPolicy GetMsalRetryPolicy()
{
// Retry policy at token request level
TimeSpan retryAfter = TimeSpan.Zero;
var retryPolicy = Policy.Handle<Exception>(ex =>
{
return IsMsalRetryableException(ex, out retryAfter);
}).WaitAndRetryAsync(new[] // simple retry 0s, 3s, 5s + and "retry after" hint from the server
{
retryAfter, // could be 0
retryAfter + TimeSpan.FromSeconds(2),
retryAfter + TimeSpan.FromSeconds(5),
},
onRetry: (ex, ts) =>
// Do some logging
Debug.WriteLine($"MSAL call failed. Trying again after {ts}. Exception was {ex}"));
return retryPolicy;
}
/// <summary>
/// Retry any MsalException marked as retryable - see IsRetryiable property and HttpRequestException
/// If Retry-After header is present, return the value.
/// </summary>
/// <remarks>
/// In MSAL 4.47.2 IsRetryiable includes HTTP 408, 429 and 5xx AAD errors but may be expanded to transient AAD errors in the future.
/// </remarks>
private static bool IsMsalRetryableException(Exception ex, out TimeSpan retryAfter)
{
retryAfter = TimeSpan.Zero;
if (ex is HttpRequestException)
return true;
if (ex is MsalException msalException && msalException.IsRetryable)
{
if (msalException is MsalServiceException msalServiceException)
{
retryAfter = GetRetryAfterValue(msalServiceException.Headers);
}
return true;
}
return false;
}
private static TimeSpan GetRetryAfterValue(HttpResponseHeaders headers)
{
var date = headers?.RetryAfter?.Date;
if (date.HasValue)
{
return date.Value - DateTimeOffset.Now;
}
var delta = headers?.RetryAfter?.Delta;
if (delta.HasValue)
{
return delta.Value;
}
return TimeSpan.Zero;
}
The example above shows how to introduce a retry policy at MSAL level, since MSAL transforms HTTP errors 5xx into MSAL specific exceptions. It is also possible, to use an HTTP level retry policy, which can be introduced directly via the HttpClient.
Both possibilities are valid, with a slight preference for library-level retry policy. The reason for this is that we are trying to classify more exceptions as retry-able, for example AAD error AADSTS50087 (see https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes#aadsts-error-codes for full list). Tracking item
Internally, AAD uses several fallback mechanisms which help applications retrieve tokens even when AAD is struggling. Regional token issuers (ESTSR) automatically fallback to the global issuer if they go down. An emergency token issuer, which does not depend on Azure infrastructure, takes over if AAD goes down.
Client applications do not need to do anything to benefit from these measures.
When the Service Token Server (STS) is too busy or having problems, it returns an HTTP error 429. It may also return other HTTP error codes, such as 503. Alongside the response it will add a Retry-After header, which indicates that the client should wait before calling again. The wait delay is in seconds, as per spec.
MsalServiceException
surfaces System.Net.Http.Headers.HttpResponseHeaders
as a property named Headers
. You can therefore leverage additional information to the Error code to improve the reliability of your applications. In the case we just described, you can use the RetryAfter
property (of type RetryConditionHeaderValue
) and compute when to retry.
- Home
- Why use MSAL.NET
- Is MSAL.NET right for me
- Scenarios
- Register your app with AAD
- Client applications
- Acquiring tokens
- MSAL samples
- Known Issues
- Acquiring a token for the app
- Acquiring a token on behalf of a user in Web APIs
- Acquiring a token by authorization code in Web Apps
- AcquireTokenInteractive
- WAM - the Windows broker
- .NET Core
- Maui Docs
- Custom Browser
- Applying an AAD B2C policy
- Integrated Windows Authentication for domain or AAD joined machines
- Username / Password
- Device Code Flow for devices without a Web browser
- ADFS support
- High Availability
- Regional
- Token cache serialization
- Logging
- Exceptions in MSAL
- Provide your own Httpclient and proxy
- Extensibility Points
- Clearing the cache
- Client Credentials Multi-Tenant guidance
- Performance perspectives
- Differences between ADAL.NET and MSAL.NET Apps
- PowerShell support
- Testing apps that use MSAL
- Experimental Features
- Proof of Possession (PoP) tokens
- Using in Azure functions
- Extract info from WWW-Authenticate headers
- SPA Authorization Code