I have a Razor page with the attribute:
[Authorize(Policy = "Staff")]
and the following authorization configuration:
options.AddPolicy("Staff", policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes("MyAdSchemeName");
policy.RequireRole("Staff");
});
When navigating to this page, it correctly redirects me to the login page, however, the return URL passed as the query string param is Account/AccessDenied
.
I feel like maybe I'm missing something, but this feels wrong as surely authentication needs to occur before it can be determined whether a 403 should be returned?
My expectation (which may be wrong) is that navigating to the page as a non-authenticated user should redirect to the login page, the login is processed (i.e. roles obtained) & then redirected back to the originally requested page where it can then determine whether they have access (i.e. the required role)?
When I remove the RequireRole
call against the policy definition, it works - but obviously allows any all authenticated users regardless of role.
As an aside, Account/AccessDenied
doesn't even exist in my app, and I cannot see how to change it. I've tried the following, but to no avail:
services.Configure<CookieAuthenticationOptions>("MyAdSchemeName", options => options.AccessDeniedPath = "/errors/403");
Program.cs code:
var authentication = services.AddAuthentication();
authentication
.AddMicrosoftIdentityWebApp(
configuration.GetSection("AdConfig"),
openIdConnectScheme: "MyAdSchemeName",
cookieScheme: null,
displayName: "MyAdSchemeName");
services.Configure<CookieAuthenticationOptions>("MyAdSchemeName", options => options.AccessDeniedPath = "/errors/403");
services.ConfigureApplicationCookie(options =>
{
options.Events = new CookieAuthenticationEvents
{
OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 403;
return Task.FromResult(0);
}
};
options.AccessDeniedPath = "/errors/403";
options.LoginPath = new PathString("/login");
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy(PolicyNames.Staff, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes("MyAdSchemeName");
policy.RequireRole("Staff");
});
});
Actually, there are multiple questions asked here and various factors involved so why don't we take a step back, clean up the configuration a bit and start with a working sample:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
// staff authentication policy
options.AddPolicy("Staff", policy =>
{
policy.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Role, "Staff");
});
});
The above demonstrates configuration of:
Account/AccessDenied
is one of the default redirects of ASP.NET Core Identity and a View with the same name is located under the Views
folder, typically. If you want to change that modify the above sample as follows:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.AccessDeniedPath = "...";
});
As a extra note, apart from using the Authorize
attribute you can apply the "Staff" policy in a whole area, which I find very convenient:
endpoints.MapControllerRoute(
name: "Staff",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}")
.RequireAuthorization("Staff");
I hope it helps and that you can further adjust it to your customization needs.