I am writing an MVC app using .net core 3.1.
I have setup user policies in Startup.cs. There is an overall Admin Role, and a User and Admin Role for each Area. The Roles exist in the AspNetRoles table, and have been linked to users in AspNetUserRoles
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAuthenticatedUserPolicy", policy => policy.RequireAuthenticatedUser());
options.AddPolicy("RequireAdmin", policy => policy.RequireRole("Admin"));
options.AddPolicy("RequireUserAnchor", policy => policy.RequireRole("Admin,AnchorUser,AnchorAdmin"));
options.AddPolicy("RequireUserDashboard", policy => policy.RequireRole("Admin,DashboardUser,DashboardAdmin"));
options.AddPolicy("RequireUserReportGroups", policy => policy.RequireRole("Admin,ReportGroupsUser,ReportGroupsAdmin"));
options.AddPolicy("RequireUserMaintenance", policy => policy.RequireRole("Admin,MaintenanceUser,MaintenanceAdmin"));
options.AddPolicy("RequireUserMaps", policy => policy.RequireRole("Admin,MapsUser,MapsAdmin"));
options.AddPolicy("RequireAdminAnchor", policy => policy.RequireRole("Admin,AnchorAdmin"));
options.AddPolicy("RequireAdminDashboard", policy => policy.RequireRole("Admin,DashboardAdmin"));
options.AddPolicy("RequireAdminReportGroups", policy => policy.RequireRole("Admin,ReportGroupsAdmin"));
options.AddPolicy("RequireAdminMaintenancen", policy => policy.RequireRole("Admin,MaintenanceAdmin"));
options.AddPolicy("RequireAdminMaps", policy => policy.RequireRole("Admin,MapsAdmin"));
});
The areas have attributes on their controller definitions. eg: the GroupsController
[Area("ReportGroups")]
[Authorize(Policy = "RequireUserReportGroups")]
public class GroupsController : Controller
{
The users roles are used to modify the sites menu, showing only sections they have access to in _Layout.cshtml
@if (User.IsInRole("Admin"))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Admin" asp-controller="Admin" asp-action="Index"><i class="fas fa-2x fa-chess-king" title="Admin"></i></a>
</li>
}
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<span style="font-weight:bold; font-size:16px;">ORG<br />@User.Identity.GetOrgCode()</span>
</li>
}
@if (User.IsInRole("AnchorUser") || User.IsInRole("Admin"))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Anchor" asp-controller="Home" asp-action="Index">
<i class="fas fa-2x fa-anchor" title="Anchor"></i>
</a>
</li>
}
@if (User.IsInRole("DashboardUser") || User.IsInRole("DashboardAdmin") || User.IsInRole("Admin"))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Dashboard" asp-controller="DriverBehaviour" asp-action="Index">
<i class="fas fa-2x fa-chart-area" title="Dashboard"></i>
</a>
</li>
}
The links on the home page are rendering correctly. eg: https://localhost:44322/Maintenance/Home
When they are clicked, they redirect to eg: https://localhost:44322/Account/AccessDenied?ReturnUrl=%2FMaintenance
which is in addition to not working as expected, a 404 error.
Querying the Role membership and having it return true would indicate I have permission. Have I missed some config somewhere? And I querying the Roles incorrectly?
There is one gotcha. The AdminController returns fine. Its a basic scaffolded controller which just has a View saying "This is the Admin Controller"
Edit 2020-07-01
The solution was to change the entries in RequireRole
. It is a list of strings, not a comma separated string. This is also why the only page using RequireAdmin worked.
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAuthenticatedUserPolicy", policy => policy.RequireAuthenticatedUser());
options.AddPolicy("RequireAdmin", policy => policy.RequireRole("Admin"));
options.AddPolicy("RequireUserAnchor", policy => policy.RequireRole("Admin","AnchorUser","AnchorAdmin"));
options.AddPolicy("RequireUserDashboard", policy => policy.RequireRole("Admin","DashboardUser","DashboardAdmin"));
options.AddPolicy("RequireUserReportGroups", policy => policy.RequireRole("Admin","ReportGroupsUser","ReportGroupsAdmin"));
options.AddPolicy("RequireUserMaintenance", policy => policy.RequireRole("Admin","MaintenanceUser","MaintenanceAdmin"));
options.AddPolicy("RequireUserMaps", policy => policy.RequireRole("Admin","MapsUser","MapsAdmin"));
options.AddPolicy("RequireAdminAnchor", policy => policy.RequireRole("Admin","AnchorAdmin"));
options.AddPolicy("RequireAdminDashboard", policy => policy.RequireRole("Admin","DashboardAdmin"));
options.AddPolicy("RequireAdminReportGroups", policy => policy.RequireRole("Admin","ReportGroupsAdmin"));
options.AddPolicy("RequireAdminMaintenancen", policy => policy.RequireRole("Admin","MaintenanceAdmin"));
options.AddPolicy("RequireAdminMaps", policy => policy.RequireRole("Admin","MapsAdmin"));
});
In your example, you are using a comma delimited value for the role names (see below)
options.AddPolicy("RequireUserAnchor", policy => policy.RequireRole("Admin,AnchorUser,AnchorAdmin"));
The method "RequireRole" takes a string array or a List.
You could try to change your code to:
options.AddPolicy("RequireUserAnchor", policy => policy.RequireRole(new string[] {"Admin","AnchorUser","AnchorAdmin"}));
Hopefully this helps!