3
votes

So I have a newly created netcore application linked to my azure active directory account with middleware setup as follows:

       app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true                
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true, 
            ClientId = Configuration["Authentication:AzureAd:ClientId"],
            Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
            CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],                                 
            Events = new OpenIdConnectEvents
            {
                OnAuthenticationFailed = OnAuthenticationFailed,
                OnAuthorizationCodeReceived = OnAuthorizationCodeReceived                                           
            }
        });

My Callback path a default of "CallbackPath": "/signin-oidc", and my azure sign on url is http://localhost:20352/ with a reply url of http://localhost:20352/signin-oidc

Now I can go through the sign in process fine, but if you hit the browser back button a few times I'm hitting this blow up:

An unhandled exception occurred while processing the request.

Exception: Correlation failed. Unknown location

AggregateException: Unhandled remote failure. (Correlation failed.) Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.d__5.MoveNext()

How or where can I catch this exception to handle it accordingly? Or is there something wrong with my middleware setup that is causing this.

The two events above are never hit.

Edit: May be helpful to know the blow up URL from the browser back button is "http://localhost:20352/signin-oidc"

Which this obviously doesnt exist as a valid controller / action route

2

2 Answers

7
votes

This error when you click the back button is due to the browser re-using a call to the Authorize endpoint of your identity provider, with the old Nonce and State parameters which are no longer valid.

When your application issues an authentication challenge and initiates a redirect to your identity provider, it temporarily stores those values so that it can verify that the response and token obtained in the callback are valid. This is what is meant by "Correlation".

The reason that your event handlers are not being called is that you want to handle the RemoteFailure event instead, e.g. redirect to an error screen:

public class AuthenticationEvents : OpenIdConnectEvents
{
    public override Task RemoteFailure(FailureContext context)
    {
        context.HandleResponse();
        context.Response.Redirect("/Home/Error");
        return Task.FromResult(0);
    }
}
0
votes

Since the application is using cookie authentication with oidc and the correlation failed due to the browser back button, you can just redirect back to the initially requested page and let the default authentication flow happen. You might want to handle possible looping correlation failures too. (like below)

.AddOpenIdConnect(options =>
{
    ...
    options.Events = new OpenIdConnectEvents()
    {
        OnRemoteFailure = (r) => {
        System.Diagnostics.Debug.WriteLine("OnRemoteFailure");
        int maxFailures = 6;
        int failuresWithSeconds = 10;
        if (r.Failure != null && r.Failure.Message == "Correlation failed.")
        {
            int rapidFailures = 0;
            DateTimeOffset since = DateTime.UtcNow;
            string correlationFailures;
            if (r.Request.Cookies.TryGetValue(".correlation.failures", out correlationFailures))
            {
                int pipeIndex = correlationFailures.IndexOf('|');
                if (pipeIndex>0)
                        {
                    long secs;
                    if (int.TryParse(correlationFailures.Substring(0, pipeIndex), out rapidFailures) && long.TryParse(correlationFailures.Substring(pipeIndex +1), out secs))
                    {
                        since = DateTimeOffset.FromUnixTimeSeconds(secs);
                    }
                }
            }
            rapidFailures++;
            if (rapidFailures < maxFailures)
            {
                r.HandleResponse();
                r.Response.Cookies.Append(".correlation.failures", $"{rapidFailures}|{since.ToUnixTimeSeconds()}", new CookieOptions() { Expires = since + TimeSpan.FromSeconds(failuresWithSeconds), HttpOnly = true, Secure = true });
                r.Response.Redirect(r.Properties.RedirectUri, false);
            }
        }
        return Task.CompletedTask; 
        }
    };
});