Policy Based Authorization in Asp.Net Core Web Api

In the previous article we implement user authentication and secure our api endpoint using Authorize attribute. The Authorize attribute allowed us to make sure that only logged in users can access our api.

In a real world application we only want to allow a certain type of users to access some part of the api. In Asp.Net we accomplish this by using Roles, but in Asp.Net Core we have a different approach. That is the use of claims and policy. There are still a backward compatibility for Roles, but it doesn’t work for token based authentication.

To use the role based authorization that we have in Asp.Net we will need to add the role in claims then create policy that will check for user roles.

Advertisements

Open AccountController and update Login action to add claims for roles. We only need to add two lines of code in our Login action. First to get roles that the user belong to then add role claims.

var roles = await this.userManager.GetRolesAsync(user);
                        claims.AddRange(roles.Select(r => new Claim("role", r)));

Your Login action should look like the code below.

[HttpPost("login")]
        public async Task<IActionResult> Login([FromBody]LoginDto model)
        {
            if (ModelState.IsValid)
            {
                var user = await this.userManager.FindByEmailAsync(model.Email);
                if(user != null)
                {
                    var passwordCheck = await this.signInManager.CheckPasswordSignInAsync(user, model.Password, false);
                    if (passwordCheck.Succeeded)
                    {
                        var claims = new List<Claim>
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                            new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
                        };
                        var roles = await this.userManager.GetRolesAsync(user);
                        claims.AddRange(roles.Select(r => new Claim("role", r)));
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Tokens:Key"]));
                        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                        var token = new JwtSecurityToken(
                            this.config["Tokens:Issuer"],
                            this.config["Tokens:Audience"],
                            claims,
                            expires: DateTime.UtcNow.AddHours(3),
                            signingCredentials: credentials                            
                            );

                        return Ok(new
                        {
                            token = new JwtSecurityTokenHandler().WriteToken(token),
                            expiration = token.ValidTo
                        });
                    }

                }
                else
                {
                    return Unauthorized();
                }
            }

            return BadRequest();
        }

Then open Startup.cs and update ConfigureServices method. Create a policy for admin access, let’s call it RequireAdminAccess.

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

You ConfigureServices method should look like the code below.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<PayrollContext>(options => options
            .UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<User, IdentityRole>().AddEntityFrameworkStores<PayrollContext>()
                .AddDefaultTokenProviders();
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = Configuration["Tokens:Issuer"],
                    ValidAudience = Configuration["Tokens:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
                };
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddAuthorization(options =>
            {
                options.AddPolicy("RequireAdminAccess", policy =>
                       policy.RequireRole("Admin"));
            });
        }

Update ValuesController add Authorize attribute in Get action with policy of RequireAdminAccess.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace PayrollApp.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        [Authorize(Policy = "RequireAdminAccess")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }
    }
}
Advertisements

Open postman to test our api. Log in as user or you can register a new user and use it log in.

Now using the token that we got we will test our end point. We only have two endpoints in our api.

The first endpoint is get that will return all values, but this endpoint has a policy that only allows and Admin user to access it. If we access this endpoint using our current token we should not be able to get data from it.

Now let’s try to access the other endpoint which only return a single value. This endpoint should allow access to any logged in users, so we should be able to get the data from it.

Let’s try to get a token using an admin user. In the first part of this tutorial we seed an admin user with email “alex@localhost” and password “P@ssw0rd1!”. Let’s use this credentials to get a token for admin user and test our api once again.

Using the token that we got from admin user. Call the api for values once again. This time we should be able to get the list of values.

Now let’s try to call the other endpoint that allows all logged in users to access.

After testing our api, we can see that our policy authorization is working.

Advertisements

Related Articles:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s