asp.net-coreidentityserver4openid-connectiis-10

Htttp 400 Bad Request Request Header is too long


I am currently developing an application using Asp.net core 5.0 and Identity server 4.My OIDC authentication flow handled by Microsoft.AspNetCore.Authentication.OpenIdConnect. I deployed my application into IIS and I am getting my login screen. But after login I got the Http 400 Bad request error.Error. I checked my application cookie it contains many AspNetCore.OpenIdConnect.Nonce cookies. I deleted the cookies but doesn't solve my issue. What is the proper solution to handle this solution. I tried this but doesn't help me. I will share the code below

MVC Startup.cs

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

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
            services.AddScoped<RenewToken>();
            services.AddAuthorization(options =>
            {
                options.AddPolicy("CreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RC", "UC")));
                options.AddPolicy("ReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RR", "UR")));
            });
            services.AddControllersWithViews();
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            }).AddCookie("Cookies", options =>
            {
                options.Cookie.Name = "Cookies";
                options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
                options.SlidingExpiration = true;
            }).AddOpenIdConnect("oidc", options =>
                {
                    options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; } };
                    options.Authority = Configuration.GetSection("API:IDS4").Value;
                    options.SignInScheme = "Cookies";
                    options.SignedOutRedirectUri = Configuration.GetSection("API:WebClient").Value + "/signout-callback-oidc";
                    options.RequireHttpsMetadata = true;
                    options.ClientId = "mvc";
                    options.ClientSecret = "*****";
                    options.ResponseType = "code";
                    options.UsePkce = true;
                    options.Scope.Add("profile");
                    options.Scope.Add("mcApi");
                    options.Scope.Add("Api1");
                    options.Scope.Add("Api2");
                    options.Scope.Add("Api3");
                    options.Scope.Add("Api4");
                    options.Scope.Add("Api5");
                    options.Scope.Add("Api6");
                    options.Scope.Add("Api7");
                    options.Scope.Add("offline_access");
                    options.GetClaimsFromUserInfoEndpoint = true;
                    options.SaveTokens = true;
                    options.Events.OnRedirectToIdentityProvider = context =>
                    {
                        context.ProtocolMessage.Prompt = "login";
                        return Task.CompletedTask;
                    };

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

                            return Task.CompletedTask;
                        }
                    };
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        RoleClaimType = JwtClaimTypes.Role
                    };

                });

            
            services.AddHttpClient<IAdminService, AdminService>();
            services.AddSingleton<DataProtectionPurposeStrings>();
            services.AddSingleton<GlobalConstants>();
        }

        private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
        {
            return context.User.HasClaim(claim => claim.Type == roleClaim && claim.Value == "True") &&
                   context.User.HasClaim(claim => claim.Type == userClaim && claim.Value == "True") ||
                   context.User.IsInRole("MyAdmin");
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");                
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });

        }
    }

Identity Server Startup.cs

public class Startup
    {
        public IWebHostEnvironment Environment { get; }
        public IConfiguration Configuration { get; }
        public Startup(IWebHostEnvironment environment, IConfiguration configuration)
        {
            Environment = environment;
            Configuration = configuration;
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            /****Register asp.net core Identity DBConetexts***/
            var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
            
            var dbPassword = Configuration["DbContextSettings:DbPassword"];
            var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
            {
                Password = dbPassword
            };
            

            services.AddDbContext<IdentityDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));

            services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
            {
                options.Password.RequiredLength = 8;
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ ";
                options.SignIn.RequireConfirmedEmail = false;
                options.User.RequireUniqueEmail = false;
            }).AddRoles<ApplicationRole>().AddEntityFrameworkStores<IdentityDBContext>()
            .AddDefaultTokenProviders();

            /****Identity Server implementation with asp.net core Identity***/

            var idsServerConnectionString = Configuration["DbContextSettings:IdentityServer4ConnectionString"];
            var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            var idsServerdbPassword = Configuration["DbContextSettings:DbPassword"];
            var idsServerbuilder = new NpgsqlConnectionStringBuilder(idsServerConnectionString)
            {
                Password = dbPassword
            };
            
            var idBuilder = services.AddIdentityServer(options =>
              {
                  options.Events.RaiseErrorEvents = true;
                  options.Events.RaiseInformationEvents = true;
                  options.Events.RaiseFailureEvents = true;
                  options.Events.RaiseSuccessEvents = true;
                  options.UserInteraction.LoginUrl = "/Account/Login";
                  options.UserInteraction.LogoutUrl = "/Account/Login";
                  options.Authentication = new AuthenticationOptions()
                  {
                      CookieLifetime = TimeSpan.FromMinutes(10),
                      CookieSlidingExpiration = true
                  };
              })
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
            })
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
                options.EnableTokenCleanup = true;
            }).AddAspNetIdentity<MembershipUser>()
            .AddProfileService<ProfileService>();

            X509Certificate2 cert = null;
            using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
            {
                certStore.Open(OpenFlags.ReadOnly);
                var certCollection = certStore.Certificates.Find(
                    X509FindType.FindByThumbprint,
                    "thumbprint",
                    false);

                if (certCollection.Count > 0)
                {
                    cert = certCollection[0];
                }
            }
            if (Environment.IsDevelopment())
            {
                idBuilder.AddDeveloperSigningCredential();
            }
            else
            {
                idBuilder.AddSigningCredential(cert);
            }

            idBuilder.Services.ConfigureExternalCookie(options =>
            {
                options.Cookie.IsEssential = true;
                options.Cookie.SameSite = (SameSiteMode)(-1); 
            });

            idBuilder.Services.ConfigureApplicationCookie(options =>
            {
                options.Cookie.IsEssential = true;
                options.Cookie.SameSite = (SameSiteMode)(-1);
            });

            services.AddMediatR(typeof(Startup));

            RegisterServices(services);
        }

        private void RegisterServices(IServiceCollection services)
        {
            services.AddSingleton<IEventBus, RabbitMQBus>(sp =>
            {
                var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
                return new RabbitMQBus(sp.GetService<IMediator>(), scopeFactory);
            });

            services.AddTransient<UserDBContext>();
        }
        
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
        {
            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // uncomment if you want to add MVC
            app.UseStaticFiles();
            app.UseRouting();

            app.UseIdentityServer();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
    }

Can I store the cookies into SessionStore using MemoryCacheTicketStore ? Kindly share your thoughts.


Solution

  • A potential thing could be that IIS thinks the cookie header is too long.

    By default ASP.NET Core chunks up the cookie in 4Kb chunks, like this picture shows:

    enter image description here

    So either you try to reduce the size of the cookie or look at the IIS settings, if you can increase the max header length?

    Alternatively, you stop saving the tokens inside the cookie, by setting:

    options.SaveTokens = false;

    Now you of course need top store it somewhere else, like in a tokenstore.

    I also recently blogged about how to reduce the cookie size while also improving your security.

    Improving ASP.NET Core Security By Putting Your Cookies On A Diet