Custom cookie authentication does not work after migrating from ASP.NET Core 1.1 MVC to 2.0

I migrated the ASP.NET Core 1.1 MVC project to ASP.NET Core 2.0, and now I note that requests to unauthorized sections of the application no longer result in a "401 Unauthorized" answer, but rather with a code exception resulting in a "500 internal error" response server ".

An example of an excerpt from a log file (John Smith has no right to access the action of the controller that he was trying to access):

2018-01-02 19:58:23 [DBG] Request successfully matched the route with name '"modules"' and template '"m/{ModuleName}"'. 2018-01-02 19:58:23 [DBG] Executing action "Team.Controllers.ModulesController.Index (Team)" 2018-01-02 19:58:23 [INF] Authorization failed for user: "John Smith". 2018-01-02 19:58:23 [INF] Authorization failed for the request at filter '"Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter"'. 2018-01-02 19:58:23 [INF] Executing ForbidResult with authentication schemes ([]). 2018-01-02 19:58:23 [INF] Executed action "Team.Controllers.ModulesController.Index (Team)" in 146.1146ms 2018-01-02 19:58:23 [DBG] System.InvalidOperationException occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation. 2018-01-02 19:58:23 [DBG] Entity Framework did not record any exceptions due to failed database operations. This means the current exception is not a failed Entity Framework database operation, or the current exception occurred from a DbContext that was not obtained from request services. 2018-01-02 19:58:23 [ERR] An unhandled exception has occurred while executing the request System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultForbidScheme found. at Microsoft.AspNetCore.Authentication.AuthenticationService.<ForbidAsync>d__12.MoveNext() ... 

I use custom cookie authentication implemented as middleware. Here is my Startup.cs (app.UseTeamAuthentication () - middleware call):

 public class Startup { public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); builder.AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.Configure<MyAppOptions>(Configuration); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddDbContext<ApplicationDbContext>(options => options .ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning)) .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))); services.AddAuthorization(options => { options.AddPolicy(Security.TeamAdmin, policyBuilder => policyBuilder.RequireClaim(ClaimTypes.Role, Security.TeamAdmin)); options.AddPolicy(Security.SuperAdmin, policyBuilder => policyBuilder.RequireClaim(ClaimTypes.Role, Security.SuperAdmin)); }); services.AddDistributedMemoryCache(); services.AddSession(options => { options.IdleTimeout = System.TimeSpan.FromMinutes(5); options.Cookie.HttpOnly = true; }); services.AddMvc() .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()) .AddViewLocalization( LanguageViewLocationExpanderFormat.SubFolder, options => { options.ResourcesPath = "Resources"; }) .AddDataAnnotationsLocalization(); services.Configure<RequestLocalizationOptions>(options => { options.DefaultRequestCulture = new RequestCulture("en-US"); options.SupportedCultures = TeamConfig.SupportedCultures; options.SupportedUICultures = TeamConfig.SupportedCultures; options.RequestCultureProviders.Insert(0, new MyCultureProvider(options.DefaultRequestCulture)); }); services.AddScoped<IViewLists, ViewLists>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.File("log.txt", outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message}{NewLine}{Exception}") .CreateLogger(); loggerFactory.AddSerilog(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } bool UseHttps = Configuration.GetValue("Https", false); if (UseHttps) { app.UseRewriter(new RewriteOptions().AddRedirectToHttps()); } app.UseStaticFiles(); app.UseTeamDatabaseSelector(); app.UseTeamAuthentication(); var localizationOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>(); app.UseRequestLocalization(localizationOptions.Value); app.UseSession(); app.UseMvc(routes => { routes.MapRoute( name: "modules", template: "m/{ModuleName}", defaults: new { controller = "Modules", action = "Index" } ); routes.MapRoute( name: "actions", template: "a/{action}", defaults: new { controller = "Actions" } ); routes.MapRoute( name: "modules_ex", template: "mex/{action}", defaults: new { controller = "ModulesEx" } ); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } 

Here is the middleware:

 public class TeamAuthentication { private readonly RequestDelegate next; private readonly ILogger<TeamAuthentication> logger; public TeamAuthentication(RequestDelegate _next, ILogger<TeamAuthentication> _logger) { next = _next; logger = _logger; } public async Task Invoke(HttpContext context, ApplicationDbContext db) { if (TeamConfig.AuthDebug) { logger.LogDebug("Auth-Invoke: " + context.Request.Path); } const string LoginPath = "/Login"; const string LoginPathTimeout = "/Login?timeout"; const string LogoutPath = "/Logout"; bool Login = (context.Request.Path == LoginPath || context.Request.Path == LoginPathTimeout); bool Logout = (context.Request.Path == LogoutPath); string TokenContent = context.Request.Cookies["t"]; bool DatabaseSelected = context.Items["ConnectionString"] != null; bool Authenticated = false; bool SessionTimeout = false; // provjera tokena if (!Login && !Logout && DatabaseSelected && TokenContent != null) { try { var token = await Security.CheckToken(db, logger, TokenContent, context.Response); if (token.Status == Models.TokenStatus.OK) { Authenticated = true; context.Items["UserID"] = token.UserID; List<Claim> userClaims = new List<Claim>(); var person = await db.Person.AsNoTracking() .Where(x => x.UserID == token.UserID) .FirstOrDefaultAsync(); if (person != null) { var emp = await db.Employee.AsNoTracking() .Where(x => x.PersonID == person.ID) .FirstOrDefaultAsync(); if (emp != null) { context.Items["EmployeeID"] = emp.ID; } } string UserName = ""; if (person != null && person.FullName != null) { UserName = person.FullName; } else { var user = await db.User.AsNoTracking() .Where(x => x.ID == token.UserID) .Select(x => new { x.Login }).FirstOrDefaultAsync(); UserName = user.Login; } context.Items["UserName"] = UserName; userClaims.Add(new Claim(ClaimTypes.Name, UserName)); if ((token.Roles & (int)Security.TeamRoles.TeamAdmin) == (int)Security.TeamRoles.TeamAdmin) { userClaims.Add(new Claim(ClaimTypes.Role, Security.TeamAdmin)); } if ((token.Roles & (int)Security.TeamRoles.SuperAdmin) == (int)Security.TeamRoles.SuperAdmin) { userClaims.Add(new Claim(ClaimTypes.Role, Security.TeamAdmin)); userClaims.Add(new Claim(ClaimTypes.Role, Security.SuperAdmin)); } ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local")); context.User = principal; } else if (token.Status == Models.TokenStatus.Expired) { SessionTimeout = true; } } catch (System.Exception ex) { logger.LogCritical(ex.Message); } } if (Login || (Logout && DatabaseSelected) || Authenticated) { await next.Invoke(context); } else { if (Utility.IsAjaxRequest(context.Request)) { if (TeamConfig.AuthDebug) { logger.LogDebug("Auth-Invoke => AJAX 401"); } context.Response.StatusCode = 401; context.Response.Headers.Add(SessionTimeout ? "X-Team-Timeout" : "X-Team-Login", "1"); } else { string RedirectPath = SessionTimeout ? LoginPathTimeout : LoginPath; if (TeamConfig.AuthDebug) { logger.LogDebug("Auth-Invoke => " + RedirectPath); } context.Response.Redirect(RedirectPath); } } } } } 

Here is the same middleware, with code that I think is not important for the question that has been trimmed:

 public class TeamAuthentication { private readonly RequestDelegate next; private readonly ILogger<TeamAuthentication> logger; public async Task Invoke(HttpContext context, ApplicationDbContext db) { // preparatory actions... var token = await Security.CheckToken(db, logger, TokenContent, context.Response); if (token.Status == Models.TokenStatus.OK) { List<Claim> userClaims = new List<Claim>(); string UserName = ""; // find out the UserName... userClaims.Add(new Claim(ClaimTypes.Name, UserName)); if ((token.Roles & (int)Security.TeamRoles.TeamAdmin) == (int)Security.TeamRoles.TeamAdmin) { userClaims.Add(new Claim(ClaimTypes.Role, Security.TeamAdmin)); } if ((token.Roles & (int)Security.TeamRoles.SuperAdmin) == (int)Security.TeamRoles.SuperAdmin) { userClaims.Add(new Claim(ClaimTypes.Role, Security.TeamAdmin)); userClaims.Add(new Claim(ClaimTypes.Role, Security.SuperAdmin)); } ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local")); } // ... 

This is how I allow access to the controller:

 namespace Team.Controllers { [Authorize(Policy = Security.TeamAdmin)] public class ModulesController : Controller { // ... 

I tried to research the problem on Google and found articles like https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x and some similar ones, but they didn’t help me solve the problem .

+7
authentication c # authorization asp.net-core asp.net-core-mvc
source share
1 answer

IMHO , you can switch to the built-in authorization of the basic role instead of rewinding your own custom political authorization , there will certainly be cases that you did not think that they are being processed by him (without reinventing the wheel :).

For authentication, you must set up a cookie verification scheme using

 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); 

Check out the settings he provides here for a custom schema without an ASP.Net ID.

As for authorization, you have mixed authentication and authorization here, middleware does both, but is called UseTeamAuthentication , this explains the difference, and as such, two things are separated in the ASP.Net Core infrastructure.

Authorization, as you did (required), must be done by implementing requirements through the IAuthorizationRequirement interface, you can read how to do this in the above link to the political policy. But I highly recommend that you use the built-in role mechanism.

Greetings :)

+2
source share

All Articles