2
votes

I have a series of web pages and the authorization to those pages is defined in a custom database table. For example, I have a role called "superuser" and that role is allowed access on certain web pages. I have users assigned to that role.

I don't understand how I can put an Authorize attribute on a controller and pass in a page name (a view) and then have a custom handler of some type read from my database to see if the user is in a group that has permission. I've been reading up on policy-based authorization here: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.2 and trying to make sense of it for my situation.

Am I on the right track with policy based authorization or is there another way to do a database check for permission before allowing the user to access the page?

2

2 Answers

5
votes

The Authorize attribute, in itself, only serves to specify the kind of authorization you need on a particular page or controller. This attribute is meant to be used in addition to the Identity framework, and can include roles, policies, and authentication schemes.

What you need is to create a bridge between the Identity framework and your database, which can be accomplished with custom UserStore and RoleStore, which is described in details on this page.

To summarize a pretty complex process:

  1. The Authorize attribute instructs the browser to authenticate your user
  2. Your user is redirected to the authentication page
  3. If it succeeds, you're provided with a ClaimsPrincipal instance, that you then need to map to your database user, via the custom UserStore
  4. Your user can then be checked against DB roles

Here's a short example of all this in action (NOT fully complete, because it would be far too much code).

Startup.cs

// This class is what allows you to use [Authorize(Roles="Role")] and check the roles with the custom logic implemented in the user store (by default, roles are checked against the ClaimsPrincipal roles claims)
public class CustomRoleChecker : AuthorizationHandler<RolesAuthorizationRequirement>
{
    private readonly UserManager<User> _userManager;

    public CustomRoleChecker(UserManager<User> userManager)
    {
        _userManager = userManager;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
    {
        var user = await _userManager.GetUserAsync(context.User);

        // for simplicity, I use only one role at a time in the attribute
        var singleRole = requirement.AllowedRoles.Single();
        if (await _userManager.IsInRoleAsync(user, singleRole))
            context.Succeed(requirement);
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services
    .AddIdentity<User, Role>()
    .AddUserStore<MyUserStore>()
    .AddRoleStore<MyRoleStore>();

    // custom role checks, to check the roles in DB 
   services.AddScoped<IAuthorizationHandler, CustomRoleChecker>();
}

where User and Role are your EF Core entities.

MyUserStore

public class MyUserStore : IUserStore<User>, IUserRoleStore<User>, IQueryableUserStore<User>
{
    private Context _db;
    private RoleManager<Role> _roleManager;
   ...

    public async Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
    {
        // bridge your ClaimsPrincipal to your DB users
        var user = db.Users.SingleOrDefault(_ => _.Email.ToUpper() == normalizedUserName);
        return await Task.FromResult(user);
    }

   ...
    public async Task<bool> IsInRoleAsync(User user, string roleName, CancellationToken cancellationToken)
    {
        if (roleName == null)
            return true;

        // your custom logic to check role in DB
        var result = user.Roles.Any(_ => _.RoleName == roleName);
        return await Task.FromResult(result);
    }
0
votes

.Net Core -> if you going to use policy based approach, You have to define policy definition in ConfigureServices method in startup.cs

Example:

 services.AddAuthorization(options =>
            {
                options.AddPolicy("UserPolicy", policy => policy.RequireRole("USER"));
            });

Then u can apply the policy like below in controller or action method.

Authorize(Policy = "UserPolicy")