OIDC login fails with 'Correlation failed' - 'cookie not found' while cookie is present

Viewed 11775

I'm using IdentityServer 4 to provide authentication and autorisation for my web app, using an external login provider (Microsoft).

This works fine when I run both IdentityServer and my web app locally. However, when I publish the Identityserver project to Azure, it no longer works.

When I connect my locally running web app to the published IdentityServer, after returning from the Microsoft login page, the web app fails with the error 'Correlation failed. unknown location'.

The output from the web app shows:

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: 
Warning: '.AspNetCore.Correlation.oidc.xxxxxxxxxxxxxxxxxxx' cookie not found.

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: 
Information: Error from RemoteAuthentication: Correlation failed..

However, when I check my browser, a cookie with the exact name '.AspNetCore.Correlation.oidc.xxxxxxxxxxxxxxxxxxx' does exist..

Here's the startup.cs from the web app:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services
            .AddTransient<ApiService>();



        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie()
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";

            options.Authority = Configuration.GetSection("IdentityServer").GetValue<string>("AuthorityUrl"); 
            //options.RequireHttpsMetadata = true;

            options.ClientId = "mvc";
            options.ClientSecret = "secret";
            options.ResponseType = "code id_token";

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("api1");
            options.Scope.Add("offline_access");
        });

        services.AddLocalization(options => options.ResourcesPath = "Resources");
        services.Configure<RequestLocalizationOptions>(options =>
        {
            var supportedCultures = new[]
            {
                new CultureInfo("nl-NL"),
                new CultureInfo("en-US")
            };
            options.DefaultRequestCulture = new RequestCulture("nl-NL", "en-US");
            options.SupportedCultures = supportedCultures;
            options.SupportedUICultures = supportedCultures;
        });

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization();

        services.AddMvc(config =>
        {
            var policy = new AuthorizationPolicyBuilder()
                             .RequireAuthenticatedUser()
                             .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseAuthentication();
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
               name: "areas",
               template: "{area:exists}/{controller}/{action}/{id?}");

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

For me, Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler was logging this error: '".AspNetCore.Correlation.OpenIdConnect.84ee_7zFbvb_w264b0SPRmS1OTKCeDhmzQ6awHoJ5gA"' cookie not found.

After looking at Chrome developer tools, I could see that the Correlation cookie was being stripped by the browser because the cookie's SameSite attribute was set to "None" yet the "Secure" attribute was not set. Chrome didn't like this.

I added the following statement in my Startup.Configure method.
*NOTE: This has to be added before app.UseAuthentication() and app.UseAuthorization().

app.UseCookiePolicy(new CookiePolicyOptions
{
    Secure = CookieSecurePolicy.Always
});

I have resolved this issue by adding below code

 options.Events = new OpenIdConnectEvents
                {
                    OnMessageReceived = OnMessageReceived,
                     OnRemoteFailure = context => {
                         context.Response.Redirect("/");
                         context.HandleResponse();

                         return Task.FromResult(0);
                     }

                };

The problem was that the IdentityServer was still using AddDeveloperSigningCredential

which worked fine locally, but not in Azure. By adding a certicate and this code it worked perfectly:

X509Certificate2 cert = null;
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
    certStore.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection certCollection = certStore.Certificates.Find(
        X509FindType.FindByThumbprint,
        // Replace below with your cert's thumbprint
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        false);
    // Get the first cert with the thumbprint
    if (certCollection.Count > 0)
    {
        cert = certCollection[0];
        Log.Logger.Information($"Successfully loaded cert from registry: {cert.Thumbprint}");
    }
}

// Fallback to local file for development
if (cert == null)
{
    cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "example.pfx"), "exportpassword");
    Log.Logger.Information($"Falling back to cert from file. Successfully loaded: {cert.Thumbprint}");
}

Finally I fixed it by

  app.UseCookiePolicy(new CookiePolicyOptions
        {
            MinimumSameSitePolicy = SameSiteMode.None
        });

Before applying the change, I noticed the cookie's same site was marked as Strict.

I changed the code to SameSiteMode.Lax, and it didn't work.

So I changed again to the SameSiteMode.None, it works!

Related