When authorizing requests in ASP.NET Core Web API projects, it’s not enough to know the endpoint and the user; we also need to be aware of the specific resource the user is trying to access. In this article, we will learn how to tackle these situations using resource-based authorization.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

When We Might Need to Use Resource-Based Authorization

For some actions, we can tell whether we should allow a user to perform them based only on the action and the user. For instance, in a blogging application, we might want to allow all users with the ‘Writer’ role to create a blog post. The role is a property of the user, and the action is creating a blog post.

However, let’s consider the same application but a different action. Perhaps we want to allow users to edit their own blog posts, but not other people’s. In this case, there’s no way to accomplish this only with information about the user and the action; we need access to the specific resource—the specific blog post—they’re trying to access so that we can check whether they’re the author or not.

In these scenarios, we can use resource-based authorization. This will let us access the user and action while making our authorization decision and also the concerned resource itself.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In short, we should use resource-based authorization when our authorization decision depends on the resource the user is trying to access.

How to Use Resource-Based Authorization

To use resource-based authorization, we’ll first create a requirement. As the name implies, the user’s request must meet the requirement for authorization to succeed.

We won’t write any logic in the requirement itself; for that, we’ll write an authorization handler that contains the logic for our requirement.

We’ll then wire up an authorization policy which we’ll configure to use our requirements.

Finally, we’ll imperatively call the authorization service to see whether we should allow the request to proceed.

How to Create the Authorization Requirement

Because an authorization requirement doesn’t contain any logic, we can define it very easily:

public class UserIsAuthorRequirement : IAuthorizationRequirement { }

We implement the IAuthorizationRequirement interface to identify this class as an authorization requirement.

How to Create the Authorization Handler for Our Requirement

We’ll extend the AuthorizationHandler base class from ASP.NET Core’s authorization system in order to implement the logic associated with our authorization requirement:

public class UserIsAuthorAuthorizationHandler : AuthorizationHandler<UserIsAuthorRequirement, BlogPost>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        UserIsAuthorRequirement requirement,
        BlogPost resource)
    {
        if (context.User.Identity?.Name == resource.AuthorName)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

The AuthorizationHandler class usually takes one generic type parameter which is the IAuthorizationRequirement it handles; in our case, this would be UserIsAuthorAuthorizationRequirement. However, when using resource-based authorization, it also takes in a second type parameter: the type of resource for which it authorizes requests—for our example, we’ll take in a BlogPost.

We override the HandleRequirementAsync method to add our authorization logic – here, we simply compare the user’s name against the blog post’s author’s name. When we want to authorize the request, we call the Succeed method on the AuthorizationHandlerContext passed to us providing it the IAuthorizationRequirement that has been met: UserIsAuthorRequirement in our case.

We need to specify which IAuthorizationRequirement has been met because sometimes, as we’ll discuss shortly, we can have an authorization handler dealing with multiple requirements.

Registering the Handler With the Dependency Injection Container

Let’s add our handler to the service collection so that the framework can discover it:

builder.Services.AddTransient<IAuthorizationHandler, UserIsAuthorAuthorizationHandler>();

We should select the most specific service scope possible (transient, then request-scoped, then singleton). This helps us avoid bad practices like relying on an application-wide state. We can’t depend on a more specific service than UserIsAuthorAuthorizationHandler, so we have to keep that in mind while selecting its scope.

For example, if we depend on a request-scoped database, we can’t make UserIsAuthorAuthorizationHandler transient. Instead, we should go for a request-scoped service.

In our case, we don’t have any dependencies and we can use a transient scope.

Adding the Policy for the Action

We’ll add the resource-based authorization requirement to an authorization policy the same way we add any other authorization requirement:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("UserIsAuthorPolicy", policy => policy.Requirements.Add(new UserIsAuthorRequirement()));
});

Using the Authorization Service to Evaluate the Policy

With the setup out of the way, we can finally start using our policy in our application!

We’ll start by injecting IAuthorizationService—we’ll use it to imperatively authorize the request in our action. We have to do this rather than simply adding attributes to the action because we first need to fetch the resource that our authorization requirement will be evaluated against.

Let’s inject it through the constructor of our controller:

private readonly IAuthorizationService _authorizationService;

public BlogPostsController(IAuthorizationService authorizationService)
{
    _authorizationService = authorizationService;
}

We can now invoke the AuthorizeAsync method of the authorization service passing it the user’s ClaimsPrincipal, the resource (the blog post in our case), and the policy name (we’ve called it UserIsAuthorPolicy):

[HttpPut("{blogPostId}")]
public async Task<IActionResult> UpdateBlogPost(string blogPostId)
{
    BlogPost blogPost = await _blogPostsRepository.GetByIdAsync(blogPostId);

    var authorizationResult = await _authorizationService.AuthorizeAsync(User, blogPost, "UserIsAuthorPolicy");
}

With this, we’ll have access to an AuthorizationResult. We will use this result to determine whether the user is authorized to edit this blog post. If they aren’t, the AuthorizationResult.Failure property contains the reasons why.

Let’s make sure the user is authorized—if the Succeeded property is false, we’ll return a 403 (forbidden) response:

if (!authorizationResult.Succeeded)
{
    return Forbid();
}

As a side note, a more appropriate response if the user isn’t authenticated in the first place is 401 (unauthorized). We can let the framework handle this for us by adding an AuthorizeAttribute to our action:

[Authorize]
[HttpPut("{blogPostId}")]
public async Task<IActionResult> UpdateBlogPost(string blogPostId)

It will return a 401 response for unauthenticated users straight away without ever executing our action.

Simpler Way to Handle Different Operations for a Resource

In a more realistic application, we wouldn’t just have an update action for blog posts; we’ll also be able to read blog posts, create them and delete them. It can be tedious to write a separate authorization handler for each possible permutation of an operation and resource.

Instead of creating an authorization requirement class for all the different authorization checks we want to place on the user, we can use the OperationAuthorizationRequirement class to define the specific operations we can perform. For instance, we can define the Create, Read, Update and Delete operations and reuse them across our resources. The authorization handler will decide how it should authorize users for a particular resource and operation.

Let’s start by defining authorization requirements for possible operations in a static class:

public static class CrudOperationRequirements
{
    public static OperationAuthorizationRequirement CreateRequirement =
        new OperationAuthorizationRequirement() { Name = nameof(CreateRequirement) };

    public static OperationAuthorizationRequirement ReadRequirement =
        new OperationAuthorizationRequirement() { Name = nameof(ReadRequirement) };
    
    public static OperationAuthorizationRequirement UpdateRequirement =
        new OperationAuthorizationRequirement() { Name = nameof(UpdateRequirement) };

    public static OperationAuthorizationRequirement DeleteRequirement =
        new OperationAuthorizationRequirement() { Name = nameof(DeleteRequirement) };
}

We must make sure that the name for each operation is unique because we’ll use it to identify the operation. In our case, we’ll use the name of the static property.

We’ll then write a single authorization handler to handle each of these operations:

public class BlogPostCrudOperationsAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, BlogPost>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        OperationAuthorizationRequirement requirement,
        BlogPost blogPost)
    {
        switch (requirement.Name)
        {
            case nameof(CrudOperationRequirements.CreateRequirement):
            {
                if (context.User.HasClaim(ClaimTypes.Role, "Author"))
                {
                    context.Succeed(requirement);
                }

                break;
            }

            case nameof(CrudOperationRequirements.ReadRequirement):
            {
                context.Succeed(requirement);

                break;
            }

            case nameof(CrudOperationRequirements.UpdateRequirement):
            case nameof(CrudOperationRequirements.DeleteRequirement):
            {
                if (context.User.Identity?.Name == blogPost.AuthorName)
                {
                    context.Succeed(requirement);
                }

                break;
            }
        }

        return Task.CompletedTask;
    }
}

Depending on the OperationAuthorizationRequirement, we check whether the user is authorized to perform the operation or not. For read operations, we don’t check any conditions and authorize the request right away; for the update and delete requirements, we compare their name against the post’s author’s name; and for the create requirement, we ensure they have the Author role. In all cases, we have access to the specific resource to authorize against.

Bear in mind that, here, for some requirements, we don’t need access to the blog post. If we want, we can declaratively authorize against them using the Authorize keyword instead.

To make ASP.NET Core Authorization aware of this AuthorizationHandler, let’s register it as a service:

builder.Services.AddTransient<IAuthorizationHandler, BlogPostCrudOperationsAuthorizationHandler>();

We’ll then authorize against these requirements with the IAuthorizationService as before:

[Authorize]
[HttpPut("{blogPostId}")]
public async Task<IActionResult> UpdateBlogPostAsync(string blogPostId)
{
    BlogPost blogPost = await _blogPostsRepository.GetByIdAsync(blogPostId);

    var authorizationResult = await _authorizationService.AuthorizeAsync(User, blogPost,
        CrudOperationRequirements.UpdateRequirement);

    if (!authorizationResult.Succeeded)
    {
        return Forbid();
    }

    return Ok(blogPost);
}

Conclusion

Declarative authorization will suffice for a lot of use cases. Sometimes, though, we’ll need to access a resource to make an authorization decision. In these instances, we can use resource-based authorization. This gives us more flexibility in the inputs we have access to for determining whether a user may do something.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!