DEV Community

Cover image for Understanding Role-Based Access Control with ASP.NET Web APIs
Aditya Oberai
Aditya Oberai

Posted on • Updated on

Understanding Role-Based Access Control with ASP.NET Web APIs

When you're building an application and want to restrict access to resources and information for different kinds of users, Role-Based Access Control (RBAC) is one of the best, most structured models that you can implement 🔐

What is Role-Based Access Control?

Role-Based Access Control (or RBAC) is a security model that restricts system access based on the roles assigned to users within an org. It provides a way to manage user permissions by associating users with specific roles and granting access to resources on their basis.

A 3D diagram representing how RBAC works (image source: SuperTokens)

For e.g., in a WhatsApp group chat, you have a normal user and an admin. While the user can access necessary services such as sending and reading messages, admins get extra tooling like creating invite links or adding/removing users. This is how RBAC works in essence.

Implementing RBAC in ASP.NET Web APIs

Having spent a fair amount of time building web APIs with .NET, one of the easiest ways to implement RBAC that I discovered was by adding roles as claims in JWTs.

In case you aren't aware of what JWTs are, check out this Twitter thread I wrote last week:

RBAC is implemented via JWTs in ASP.NET through the following steps:

Step 1️⃣: Define roles for your application

Identify the different roles that users can have in your system. Examples of roles could be "admin," "everyone," "moderator," etc.

Step 2️⃣: Assign the roles

Associate specific roles with individual users. This can be stored in a database or any other persistent storage.

Step 3️⃣: Generate JWT

When a user authenticates, generate a JWT that includes the user's roles as claims in the payload.

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["JWT:SecretKey"]);

var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new Claim[]
    {
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.GivenName, user.Name),
        new Claim(ClaimTypes.Role, user.Role)
    }),
    IssuedAt = DateTime.UtcNow,
    Issuer = _configuration["JWT:Issuer"],
    Audience = _configuration["JWT:Audience"],
    Expires = DateTime.UtcNow.AddMinutes(30),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
};

var token = tokenHandler.CreateToken(tokenDescriptor);
Enter fullscreen mode Exit fullscreen mode

The following NuGet packages are necessary for this purpose and must be imported at the top of your file via using directives, as shown:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
Enter fullscreen mode Exit fullscreen mode

Step 4️⃣: Protect API Endpoints

In your ASP.NET Web API controllers or actions that require authorization, apply the [Authorize] attribute to specify that the endpoint requires authentication. These may include specific roles as well.

// GET: auth/test
[Authorize(Roles = "Everyone")]
[HttpGet]
public IActionResult Test()
{
    .
    .
    .
Enter fullscreen mode Exit fullscreen mode

Step 5️⃣: Validate JWTs

Implement a JWT validation mechanism in your API to verify the authenticity and integrity of incoming JWTs. This may involve validating the issue, audience, signature, and token expiry.

builder.Services.AddAuthentication(opt =>
{
    opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{   
    opt.RequireHttpsMetadata = false; // for development only
    opt.SaveToken = true;
    opt.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["JWT:SecretKey"])),
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["JWT:Issuer"],
        ValidateAudience = true,
        ValidAudience = builder.Configuration["JWT:Audience"]
    };
});
Enter fullscreen mode Exit fullscreen mode

Step 6️⃣: Extract Roles from JWT

Once the JWT is validated, you can extract the user's roles from the JWT claims.

Step 7️⃣: Authorize Access

Finally, based on the roles extracted, implement the logic to allow or deny access to resources or actions within your API.

// GET: auth/test
[Authorize(Roles = "Everyone")]
[HttpGet]
public IActionResult Test()
{
    string token = Request.Headers["Authorization"];

    if (token.StartsWith("Bearer"))
    {
        token = token.Substring("Bearer ".Length).Trim();
    }
    var handler = new JwtSecurityTokenHandler();

    JwtSecurityToken jwt = handler.ReadJwtToken(token);

    var claims = new Dictionary<string, string>();

    foreach (var claim in jwt.Claims)
    {
        claims.Add(claim.Type, claim.Value);
    }

    return Ok(claims);
}
Enter fullscreen mode Exit fullscreen mode

If you'd like to see and try a very simplified sample of RBAC implemented via JWTs in .NET, check out this project:

GitHub logo adityaoberai / JWTAuthSample

ASP.NET Web API sample to showcase JWT Token Authentication in .NET 6

JWT Authentication .NET Sample

Description

The JWT Authentication .NET Sample is an sample ASP.NET Web API to help understand how role based authentication can be implemented via JWTs in a .NET 6 application. It utilizes an InMemory database using Entity Framework Core for storing user data and the BCrypt library for encrypting passwords.

The API has 1 controller:

  • AuthController: Contains the login, registration, and test APIs

AuthController

The AuthController contains the login, registration, and test APIs we are using to get and try the JWT token authentication.

  • POST /auth/login

    • Returns the JWT token along with the user information from the database after the user enters their email and password.

    • Post Http Request Link: https://<YOUR-DOMAIN:PORT>//auth/login

    • Request Body Example:

      {
          "userName": "adityaoberai1"
          "password": "test123"
      }
      Enter fullscreen mode Exit fullscreen mode
    • Response Example:

      {
          "userName": "adityaoberai1"
          "name": "Aditya",
          "role": "Everyone
      Enter fullscreen mode Exit fullscreen mode

Conclusion

To conclude, by implementing RBAC using JWTs, you can provide fine-grained access control to your resources based on the roles assigned to users, and allows for flexibility and scalability in managing access privileges within your application.

Top comments (0)