Search code examples
c#polly

Combining transient fault catching with reauthorization in Polly


I'm writing a mobile app with Xamarin, so I need a policy to retry network requests. The API that my app calls provides a JWT that expires, so I need to automatically reauthenticate if the relevant response is received. This seems like it should be trivial with Polly, and the Polly wiki even says it's trivial, but I can't get it to work.

The ultimate goal is:

  • Retry requests 4 times, 2 seconds apart, in the event of a network issue
  • If the request ever succeeds and returns a valid response, then completely break from Polly because we got what we need
  • If the request succeeds but returns a 401 (meaning we need to reauthenticate):
    • Use saved credentials and reauthenticate automatically
    • If credentials were valid, go back to the outer policy of retrying for network failures
    • If credentials are invalid (the auth endpoint returns a 401), then break and log the user out

Here's my code. If the response is successful, this works correctly. It also retries as necessary for network failures. The problem is that when a 401 is received, that response is always returned from the policy. The reauthentication is called and returns successfully, but Polly breaks there--the original request isn't retried, and the original 401 response is returned.

// example
var result = await RetryPolicy.ExecuteAndCaptureAsync(() => _client.SendAsync(request));

// policies
public static PolicyWrap<HttpResponseMessage> RetryPolicy
{
    get => WaitAndRetryPolicy.WrapAsync(ReAuthPolicy);
}

private static IAsyncPolicy WaitAndRetryPolicy
{
    get => Policy
        .Handle<WebException>()
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(4, _ => TimeSpan.FromSeconds(2));
}

private static IAsyncPolicy<HttpResponseMessage> ReAuthPolicy
{
    get => Policy
        .HandleResult<HttpResponseMessage>(x => x.StatusCode == HttpStatusCode.Unauthorized)
        .RetryAsync(retryCount: 1, onRetryAsync: (_, __) => App.CoreService.LogInWithSavedCredsAsync(true));
}

Solution

  • Turns out my code was correct after all, I just wasn't setting the authorization credentials properly in my LogInWithSavedCredentials function.