Search code examples
c#asp.net-mvcasp.net-coreroles

C# MVC Controllers returning Access Denied


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"));
});

Solution

  • 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!