Referbruv!


Card image cap

Role-based and Claims-based Authorization in ASP.NET Core using Policies - Hands on

ASP.NET Core Identity  • Posted 4 months ago

We have so far seen how we can implement authentication and authorization using JWT Bearer tokens in ASP.NET Core which prove to be an easy and elegant way of securing API endpoints against unauthorized or unwanted access when exposed to the Internet. In this article let's talk about two important use cases of authorizing APIs basing on an user role defined by the system and authorizing a user basing on an incoming request claims from the user token. While these two may seem to be of different in their functionalities and usage cases, they both branch out from the Policy based authorization that the ASP.NET Core provides us with.

Role-based Authorizing ASP.NET Core APIs using Policies:

A role is a designation or an earmarking assigned to a specific user which serves a purpose. For example, an admin, an author, an editor or a librarian are all roles performed by specific users or people who are assigned with them. And these roles provide certain levels of access restriction and abstraction with them, such as not all users are granted access to book keeping system except the librarian. And not all users can edit posts except the editors and not all editors can delete the post schema except the administrators. While these are differentiated in real-world at the authentication levels, by providing with separate logins or separate portals, we can also restrict access levels for such endpoints by passing in extra attributes for the earmarked users. And in the world of token based authentication and authorization systems, its made simple by the different ClaimTypes provided within the ClaimsIdentity libraries. And we configure the token authorization middleware to look for a certain roles to be allowed access to the decorated endpoints. In such way, we can restrict the access to specific endpoints form the users.

For example, let's consider the previous example of a Policy based authorization, where we have restricted access permissions to the GET AllReaders endpoint for only those users who are also Readers. While this indirectly implements a Role-based authorization if we observe carefully, there's another simpler approach to this: the Role attribute which is present for every User. We can leverage the role-based access by means of this User property by passing it along with the token claims. And we then check for the Role attribute and verify whether it belongs to only that particular Role it is meant for - the Reader in this case. And we permit access to requests which satisfy this requirement while all other requests are Forbidden (403) from access.

Example:

// The Roles expects a comma separated list of Role values
      [Authorize(Roles = "Admin,Editor")]
      [Route("all")]
      [HttpGet]
      public List<Reader> Get()
      {
          return ReaderStore.Readers;
      }

wherein generating a token for the user post authentication, we include an additional claim of Role pertaining to the user.


    public class TokenManager : ITokenManager
    {
        public AuthToken Generate(User user)
        {
            List<Claim> claims = new List<Claim>() {
                new Claim (JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim (JwtRegisteredClaimNames.Email, user.EmailAddress),
                new Claim (JwtRegisteredClaimNames.Sub, user.Id.ToString()),
		// Add the ClaimType Role which carries the Role of the user
                new Claim (ClaimTypes.Role, user.Role)
            };

            JwtSecurityToken token = new TokenBuilder()
            .AddAudience(TokenConstants.Audience)
            .AddIssuer(TokenConstants.Issuer)
            .AddExpiry(TokenConstants.ExpiryInMinutes)
            .AddKey(TokenConstants.key)
            .AddClaims(claims)
            .Build();

            string accessToken = new JwtSecurityTokenHandler().WriteToken(token);

            return new AuthToken() {
                AccessToken = accessToken,
                ExpiresIn = TokenConstants.ExpiryInMinutes
            };
        }
    }

Alternative Take:

This can also be implemented by creating a policy and adding an AuthorizationHandler which inspects the token for Role attribute and checks whether the attribute satisfies a given role.


   public class Startup
   {
	public void ConfigureServices(IServiceCollection services)
	{
	    .....

	    services.AddAuthorization(config =>
            {
                config.AddPolicy("ShouldBeAReader", options =>
                {
                    options.RequireAuthenticatedUser();
                    options.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
                    options.Requirements.Add(new ShouldBeAReaderRequirement());
                });
		
		// Add a new Policy with requirement to check for Admin
                config.AddPolicy("ShouldBeAnAdmin", options =>
                {
                    options.RequireAuthenticatedUser();
                    options.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
                    options.Requirements.Add(new ShouldBeAnAdminRequirement());
                });
            });
	}
    }

    public class ShouldBeAnAdminRequirement
    : IAuthorizationRequirement
    {
    }

    public class ShouldBeAnAdminRequirementHandler
    : AuthorizationHandler<ShouldBeAnAdminRequirement>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            ShouldBeAnAdminRequirement requirement)
        {
            // check if Role claim exists - Else Return
            // (sort of Claim-based requirement)
            if (!context.User.HasClaim(x => x.Type == ClaimTypes.Role))
                return Task.CompletedTask;

            // claim exists - retrieve the value
            var claim = context.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role);
            var role = claim.Value;

            // check if the claim equals to either Admin or Editor
            // if satisfied, set the requirement as success
            if (role == Roles.Admin || role == Roles.Editor)
                context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }

And is called by decorating the endpoint as:


        [Authorize("ShouldBeAnAdmin")]
        [Route("all")]
        [HttpGet]
        public List<Reader> Get()
        {
            return ReaderStore.Readers;
        }

Claims-based Authorizing ASP.NET Core APIs using Policies:

The Payload section of the JWT Token contain a list of user attributes in the form of a JSON (JavaScript Object Notation) Object. When a token with such a payload is validated or parsed in the Authorization middleware, this JSON object is translated into a list of KeyValuePairs defined by the IdentityFramework as Claims. A Claim is nothing but a Key Value pair of a user data; where the Key is a string and the Value is an object. There a certain list of Key names listed by the Identity Framework which are frequently used keys during token exchanges and which are also specified in the authentication schemes (such as OAuth or OpenId). There can be certain scenarios where we would like to allow access to only those users who possess certain information with them and forbid others from access if that information isn't passed. Say for example, a librarian with a certain IdCardFlag only needs to be allowed access to the top-secret page of the library system (no puns intended). In such scenarios, we put forth a requirement for the Authorization middleware to ensure such claims exist in the incoming request token for providing access. Such a mechanism is called as a Claims-based Authorization. This can also be implemented simply by defining a new Policy for inspecting tokens, or a shorter way is to use a method call during the Policy definition. It can be simply added to the middleware as shown below:


	services.AddAuthorization(config =>
       	{
	     config.AddPolicy("ShouldContainRole", options => options.RequireClaim(ClaimTypes.Role));
	});

And is invoked in the same way as a Policy based solution as below:


        [Authorize("ShouldContainRole")]
        [Route("all")]
        [HttpGet]
        public List<Reader> Get()
        {
            return ReaderStore.Readers;
        }

In this way we can implement both Role-based and Claims-based Authorization where both the schemes internally use a policy based approach.

Also Read:

Implementing a Policy - based Authorization using ASP.NET Core and JWT Bearer Tokens

Implementing JWT Beaer Token Authentication using ASP.NET Core Middlewares

Published 4 months ago

Sponsored Links
We use cookies to improve user experience. Learn More