DEV Community

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

Posted on • Edited on

25 1 1 1 1

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.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs