asp.net-coreazure-service-fabricopenid-connectazure-ad-b2ctraefik

Correlation failed in asp.net core


My web application fails to authenticate using OpenIdConnect. Currently I see a "Correlation failed" error on OnRemoteFailure.

Context:

Startup:

    public void ConfigureServices(IServiceCollection services)
    {
        (...)

        services.AddMvc();

        (...)

        services.AddAuthorization();
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(o =>
        {
            o.ClientId = clientId;
            o.Authority = $"https://login.microsoftonline.com/{tenantId}/{signinPolicy}/v2.0";
            o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            o.SaveTokens = true;
            o.Events = new OpenIdConnectEvents
            {
                OnTokenValidated = async context =>
                {
                    (...)
                },
                OnRedirectToIdentityProvider = async context =>
                {
                    if (context.Request.Headers.TryGetValue("X-Forwarded-Prefix", out var prefix) && prefix.Count > 0 &&
                    context.Request.Headers.TryGetValue("X-Forwarded-Host", out var hostValues) && hostValues.Count > 0 &&
                    context.Request.Headers.TryGetValue("X-Forwarded-Proto", out var protoValues) && protoValues.Count > 0)
                    {
                        // Use external URL and path
                        string redirectUri = $"{protoValues.First()}://{hostValues.First()}{prefix.First()}{context.Options.CallbackPath}";
                        context.ProtocolMessage.RedirectUri = redirectUri;
                    }
                },
                OnTokenResponseReceived = async context =>
                {
                },
                OnAuthenticationFailed = async context =>
                {
                },
                OnRemoteFailure = async context =>
                {
                }
            };
            o.ConfigurationManager = new PolicyConfigurationManager($"https://login.microsoftonline.com/{tenantId}/{signinPolicy}/v2.0",
                                      new[] { signinPolicy });
        });

        (...)
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseStaticFiles();

        app.UseAuthentication();

        app.Use(async (context, next) =>
        {
            if (context.Request.Headers.TryGetValue("X-Forwarded-Prefix", out var prefix) && prefix.Count() > 0)
            {
                context.Request.PathBase = prefix.First();
            }
            await next.Invoke();
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

Controller:

public class AccountController : Controller
{
    [HttpGet]
    public IActionResult SignIn()
    {
        if (Request.Headers.TryGetValue("X-Forwarded-Prefix", out var prefix) && prefix.Count() > 0)
        {
            return Challenge(new AuthenticationProperties { RedirectUri = prefix.First() }, OpenIdConnectDefaults.AuthenticationScheme);
        }
        else
        {
            (...)
        }
    }
    (...)
}

The event OnTokenValidated is never fired.

Regarding the reverse proxy, it basically maps https://internal_url:port/internal_path to https://external_url/external_path.

I checked the requests, and this is the GET accessed:

https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?p={signinPolicy}&client_id={clientId}&redirect_uri=https%3A%2F%2F{external_host}%2F{external_path}%2Fsignin-oidc&response_type=id_token&scope=openid%20profile&response_mode=form_post&nonce=(...)&x-client-SKU=ID_NET&x-client-ver=2.1.4.0

It is successful and then the POST request that fails:

https://{external_url}/{external_path}/signin-oidc

This POST contains the form data id_token and state.

The configured redirect URL in B2C is https://{external_url}/{external_path}/signin-oidc. I tried also just https://{external_url}/{external_path}, but it did not work as well.

I tried to use Forwarded Headers, but it did not help.

Can anyone point me to the what is missing?

Thanks in advance!


Solution

  • I found the issue. The problem was in the order of the middlewares. Authentication middleware must take place after the change on PathBase. Forwarded headers are not needed in my case.

    Following the fixed Configure method.

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.Use(async (context, next) =>
        {
            if (context.Request.Headers.TryGetValue("X-Forwarded-Prefix", out var prefix) && prefix.Count() > 0)
            {
                context.Request.PathBase = prefix.First();
            }
            await next.Invoke();
        });
    
        app.UseStaticFiles();
    
        app.UseAuthentication();
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }