Role-Based Authorization
Explore how role-based authorization restricts access to ASP.NET Core endpoints based on user roles defined in JWTs. Understand the use of the Authorize attribute to enforce role-specific access at both class and method levels, and learn best practices for managing authorization rules across your application endpoints.
Role-based authorization is, perhaps, the best-known type of authorization. When it's applied to any endpoint, only the users assigned to any of the specified roles are allowed to access it. Otherwise, no access is given.
Role-based authorization and JWT
There is no strongly-defined standard of how user roles are defined in a JWT. However, a common way of sharing user roles is to have a role claim that is mapped to an array of individual role names in the JWT payload.
Here is an example of a JWT payload:
In this example, the role claim can be found in lines 11–14. In this example, there are two roles: admin and user. This means that the user for whom this token was issued would be authorized to access any endpoints that require either of these roles. The user will also still be able to access any endpoint that doesn't require either authentication or authorization.
Role-based authorization in ASP.NET Core
Specific mechanics of how role-based authorization is applied in ASP.NET Core vary across application types and endpoint types. However, in most cases, role-based authorization is controlled via the Authorize attribute with the Roles property set to a specific value.
This attribute is represented by the AuthorizeAttribute class that comes from the Microsoft.AspNetCore.Authorization namespace. This attribute can be placed in either of the following positions:
Endpoint method, such as an API controller method mapped to a specific URL path, gRPC service method, SignalR Hub method, etc.
The class that contains endpoint methods, such as the API controller, gRPC service, etc.
To make this attribute work with role-based authorization, we need to set the Roles property to a comma-separated list of the specific roles that we want to be able to access a given resource. For example, if we want to grant access to the roles called admin and superuser, the attribute will look as follows:
[Authorize(Roles = "admin,superuser")]
Let's now have a look at an example of how this attribute can be used.
Example of role-based authorization
The following playground contains an example of how role-based authorization can be applied to a web API controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DemoApp.Controllers;
[Authorize(Roles = "admin,superuser")]
[Route("api/[controller]")]
[ApiController]
public class InfoController : ControllerBase
{
[HttpGet()]
public IActionResult GetSecretInfo()
{
return Ok("Secret info delivered.");
}
[AllowAnonymous]
[HttpGet("health")]
public IActionResult GetEndpointHealth()
{
return Ok("The endpoint is working");
}
}In this example, we have the InfoController.cs file inside the Controllers folder. This controller has the following two endpoints:
api/info: It corresponds to theGetSecretInfomethod on line 12.api/info/health: It corresponds to theGetEndpointHealthmethod on line 19.
On line 6 of this file, we have the Authorize attribute configured to only allow access for the users that are assigned to either the admin or superuser role.
Because this attribute is placed above the signature of the class, all the endpoints inside this class inherit these rules by default. Therefore, if we don't apply any other authorization rules to any of the endpoints, they will all require the user to be either in the admin or the superuser role. For example, this will apply to the GetSecretInfo endpoint method on line 12 because it doesn't have any additional attributes that override these role requirements. Therefore, the corresponding endpoint will return the 401 response code if an attempt is made to access it anonymously.
However, we can also override these authorization rules and either apply different rules to any specific endpoint or allow it to be accessed without any authentication at all. An Authorize attribute placed on any individual endpoint method will override the Authorize attribute placed on the class.
If we already have the Authorize attribute set on the whole class, we would need to apply the AllowAnonymous attribute to any endpoint that we want to grant anonymous access to. We have an example of this on line 17. Even though the whole controller requires specific roles, the endpoint under this attribute, which is accessible via the api/info/health URL path, can be accessed anonymously. The endpoint will return the 200 response code with the The endpoint is working message.
Best practices for using the Authorize attribute
How do we decide whether to apply the Authorize attribute to the whole class or individual endpoints? Simply, here is the process we can follow:
If the majority of the endpoints should be accessible anonymously, we don't apply the
Authorizeattribute at the class level.If different endpoints have different authorization requirements and no two of them are the same, then we don't apply the
Authorizeattribute at the class level and instead apply it individually.If the majority of the endpoints have the same authorization requirements, we apply the
Authorizeattribute at the class level and override where necessary.