LEARN BUILD GROW TOGETHER
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /.NET /Role-Based Authorization in .NET Web API – Secure API Endpoints Based on User Roles

Role-Based Authorization in .NET Web API – Secure API Endpoints Based on User Roles

 

Introduction

In Day 16, we implemented JWT Authentication in .NET Web API. We learned how to generate JSON Web Tokens, validate incoming tokens, configure JWT Bearer authentication, and protect API endpoints using the [Authorize] attribute.

Now let's understand another important part of API security: how can we allow different users to access different API operations based on their roles?

Imagine we are developing an Employee Management application.

Our application has three types of users:

  • Admin: Can create, update, view, and delete employee records.

  • Manager: Can view employee records and manage their assigned team.

  • Employee: Can view their own profile and permitted information.

All three users can successfully log in. However, they should not have the same level of access.

For example, an employee should not be able to delete employee records simply because they have a valid authentication token.

This is where Role-Based Authorization comes into play.

Role-Based Authorization allows us to control access to API endpoints based on the roles assigned to authenticated users.

In this article, we'll understand role-based authorization, explore how roles work with JWT authentication, and implement role-based access control in a .NET Web API using practical C# examples.

1. What Is Role-Based Authorization?

Role-Based Authorization is an access-control approach in which permissions are granted based on the roles assigned to users.

In simple terms, it answers this question:

What can a user do based on their role?

For example, an Employee Management application might have the following roles:

RolePermissions
AdminCreate, view, update, and delete employee records.
ManagerView and manage assigned team members.
EmployeeView own profile and permitted information.

When a user logs in, the application authenticates their identity.

After authentication, the API can use the user's role to decide whether a particular operation is allowed.

For example:

  • An Admin can access the employee creation endpoint.

  • A Manager can access team management endpoints.

  • An Employee can access their own profile endpoint.

This helps us build applications where different users have different access levels.

2. Authentication vs Role-Based Authorization

Before implementing role-based authorization, let's understand how it differs from authentication.

AuthenticationRole-Based Authorization
Verifies user identity.Checks permissions based on user roles.
Answers: Who are you?Answers: What can you do?
Uses credentials and authentication tokens.Uses roles associated with the authenticated identity.
Establishes an authenticated user.Determines whether the user can access an endpoint.
Example: Rahul logs in successfully.Example: Rahul has the Admin role and can delete employees.

In our previous article, we learned how JWT authentication identifies users.

Now we'll extend that implementation by using role information to control access.

3. How Role-Based Authorization Works

Let's understand the process using our Employee Management application.

Suppose we have three users:

UserRole
RahulAdmin
PriyaManager
AmitEmployee

When these users log in, the application generates a JWT containing their identity and role information.

For example, Rahul's token may contain a role claim:

{
  "sub": "101",
  "name": "Rahul",
  "role": "Admin"
}

Priya's token may contain:

{
  "sub": "102",
  "name": "Priya",
  "role": "Manager"
}

Amit's token may contain:

{
  "sub": "103",
  "name": "Amit",
  "role": "Employee"
}

When a user requests a protected endpoint, the API:

  1. Receives the JWT from the client.

  2. Validates the token.

  3. Identifies the authenticated user.

  4. Reads the role claim.

  5. Checks whether the user's role is allowed to access the endpoint.

  6. Executes the request if authorization succeeds.

If the authenticated user does not have the required role, the API returns 403 Forbidden.

Important: JWT payloads are encoded, not encrypted by default. Role claims must be generated by a trusted authentication system and must never be accepted directly from client input.

4. Understanding Role Claims in JWT

A role claim represents the role assigned to an authenticated user.

For example:

new Claim(ClaimTypes.Role, user.Role)

Here, ClaimTypes.Role identifies the claim as a role claim.

When the JWT authentication handler validates the token, .NET creates an authenticated identity containing the claims.

The authorization system can then use those role claims to evaluate role requirements.

For example:

[Authorize(Roles = "Admin")]

This endpoint requires the authenticated user to have the Admin role.

We can also allow multiple roles:

[Authorize(Roles = "Admin,Manager")]

In this example, the user must have at least one of the specified roles.

5. Implementing Role-Based Authorization in .NET Web API

Now let's implement role-based authorization using our Employee Management API.

We'll build on the JWT implementation from Day 16.

Step 1: Create User Roles

First, let's define the roles used in our application.

Create a folder named Constants and add a file named UserRoles.cs.

namespace JwtAuthenticationApi.Constants;

public static class UserRoles
{
    public const string Admin = "Admin";

    public const string Manager = "Manager";

    public const string Employee = "Employee";
}

Using constants helps avoid repeatedly writing role names as string literals throughout the application.

For example:

[Authorize(Roles = UserRoles.Admin)]

This also reduces the chances of spelling mistakes.

Step 2: Update the User Model

Our previous article introduced a LoginUser model containing a Role property.

We'll continue using that property.

namespace JwtAuthenticationApi.Models;

public class LoginUser
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Email { get; set; } = string.Empty;

    public string Password { get; set; } = string.Empty;

    public string Role { get; set; } = string.Empty;
}

For this tutorial, the role is represented as a string.

In a real application, roles should be maintained in a trusted database or identity provider rather than being assigned through untrusted client requests.

Step 3: Add Role Information While Generating JWT

Now let's update the JWT token generation service from Day 16.

Open JwtTokenService.cs and ensure the role claim is included.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using JwtAuthenticationApi.Models;
using Microsoft.IdentityModel.Tokens;

namespace JwtAuthenticationApi.Services;

public class JwtTokenService
{
    private readonly IConfiguration _configuration;

    public JwtTokenService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string GenerateToken(LoginUser user)
    {
        var jwtSettings = _configuration.GetSection("Jwt");

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(jwtSettings["Key"]!));

        var credentials = new SigningCredentials(
            key,
            SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(
                JwtRegisteredClaimNames.Sub,
                user.Id.ToString()),

            new Claim(
                JwtRegisteredClaimNames.Name,
                user.Name),

            new Claim(
                JwtRegisteredClaimNames.Email,
                user.Email),

            new Claim(
                ClaimTypes.Role,
                user.Role)
        };

        var token = new JwtSecurityToken(
            issuer: jwtSettings["Issuer"],
            audience: jwtSettings["Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(
                Convert.ToDouble(
                    jwtSettings["ExpiryMinutes"])),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler()
            .WriteToken(token);
    }
}

The important addition is:

new Claim(ClaimTypes.Role, user.Role)

This adds the user's role to the JWT.

When the API validates the token, the role claim becomes available to the authorization system.

Step 4: Configure JWT Authentication

In Day 16, we configured JWT Bearer authentication in Program.cs.

We can continue using the same configuration.

For role-based authorization, make sure that the token's role claim is correctly recognized by .NET.

A typical configuration looks like this:

using System.Security.Claims;
using System.Text;
using JwtAuthenticationApi.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddScoped<JwtTokenService>();

var jwtSettings = builder.Configuration.GetSection("Jwt");

var signingKey = Encoding.UTF8.GetBytes(
    jwtSettings["Key"]!);

builder.Services
    .AddAuthentication(
        JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters =
            new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,

                ValidIssuer = jwtSettings["Issuer"],
                ValidAudience = jwtSettings["Audience"],

                IssuerSigningKey =
                    new SymmetricSecurityKey(signingKey),

                RoleClaimType = ClaimTypes.Role,

                ClockSkew = TimeSpan.FromSeconds(30)
            };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

Notice this configuration:

RoleClaimType = ClaimTypes.Role

It tells the JWT authentication handler which claim represents a user's role.

This is particularly useful when configuring role-based authorization with custom claim names.

The authentication middleware must still run before authorization middleware.

Step 5: Create Role-Protected API Endpoints

Now let's create an Employees controller with different access levels.

Create or update EmployeesController.cs.

using JwtAuthenticationApi.Constants;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JwtAuthenticationApi.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class EmployeesController : ControllerBase
{
    // Any authenticated user can access this endpoint.
    [HttpGet("profile")]
    public IActionResult GetProfile()
    {
        return Ok(new
        {
            message = "Authenticated user profile",
            name = User.Identity?.Name
        });
    }

    // Only Admin can access this endpoint.
    [HttpPost("create")]
    [Authorize(Roles = UserRoles.Admin)]
    public IActionResult CreateEmployee()
    {
        return Ok("Employee created successfully.");
    }

    // Admin and Manager can access this endpoint.
    [HttpGet("team")]
    [Authorize(Roles =
        UserRoles.Admin + "," + UserRoles.Manager)]
    public IActionResult GetTeam()
    {
        return Ok("Team information retrieved successfully.");
    }

    // Only Admin can delete employee records.
    [HttpDelete("{id}")]
    [Authorize(Roles = UserRoles.Admin)]
    public IActionResult DeleteEmployee(int id)
    {
        return Ok($"Employee {id} deleted successfully.");
    }
}

Let's understand the access requirements.

EndpointAllowed Roles
GET /api/employees/profileAny authenticated user
POST /api/employees/createAdmin
GET /api/employees/teamAdmin, Manager
DELETE /api/employees/{id}Admin

The [Authorize] attribute at the controller level requires authentication for all its endpoints.

The additional role attributes apply more specific authorization requirements.

Step 6: Understand the Login Process

Our login endpoint from Day 16 already generates a JWT containing the user's role.

For demonstration, we can use the following sample users:

var users = new List<LoginUser>
{
    new LoginUser
    {
        Id = 1,
        Name = "Rahul",
        Email = "rahul@example.com",
        Password = "Admin123",
        Role = UserRoles.Admin
    },
    new LoginUser
    {
        Id = 2,
        Name = "Priya",
        Email = "priya@example.com",
        Password = "Manager123",
        Role = UserRoles.Manager
    },
    new LoginUser
    {
        Id = 3,
        Name = "Amit",
        Email = "amit@example.com",
        Password = "Employee123",
        Role = UserRoles.Employee
    }
};

When these users log in successfully, their JWT tokens contain their respective roles.

The role information is then used by the authorization system when they access protected endpoints.

Security note: These sample users and passwords are for demonstrating the flow only. Do not use plain-text passwords or hardcoded users in a real application. Use a persistent identity store and secure password hashing.

6. Testing Role-Based Authorization

Now let's test our implementation using Postman.

Run the application:

dotnet run

Test 1: Login as Admin

Send a POST request:

POST /api/auth/login

Request body:

{
  "email": "rahul@example.com",
  "password": "Admin123"
}

The API returns a JWT containing the Admin role.

Copy the access token.

Now send:

POST /api/employees/create

Add the token in the Authorization tab using Bearer Token.

Expected response:

200 OK

The Admin can access the endpoint.

Test 2: Login as Manager

Send another login request:

POST /api/auth/login

Request body:

{
  "email": "priya@example.com",
  "password": "Manager123"
}

Copy the returned token.

Now try accessing:

GET /api/employees/team

Expected response:

200 OK

The Manager can access the team endpoint.

Now try:

POST /api/employees/create

Expected response:

403 Forbidden

The Manager is authenticated but does not have the Admin role.

Test 3: Login as Employee

Send:

POST /api/auth/login

Request body:

{
  "email": "amit@example.com",
  "password": "Employee123"
}

Use the generated token to access:

GET /api/employees/profile

Expected response:

200 OK

The Employee can access the authenticated profile endpoint.

Now try:

GET /api/employees/team

Expected response:

403 Forbidden

The Employee does not have the required Admin or Manager role.

Authorization Test Summary

UserProfileTeamCreate EmployeeDelete Employee
Rahul (Admin)AllowedAllowedAllowedAllowed
Priya (Manager)AllowedAllowedDeniedDenied
Amit (Employee)AllowedDeniedDeniedDenied

This demonstrates how role-based authorization controls API access.

7. Multiple Roles and Authorization Rules

.NET allows us to specify multiple roles in the [Authorize] attribute.

For example:

[Authorize(Roles = "Admin,Manager")]

This means that a user must belong to at least one of the specified roles.

However, there is an important distinction when multiple [Authorize] attributes are applied.

For example:

[Authorize(Roles = "Admin")]
[Authorize(Roles = "Manager")]

In this case, both authorization requirements must be satisfied. The authenticated identity must satisfy both role requirements.

This differs from specifying multiple roles in one attribute, where any matching role is sufficient.

Understanding this distinction helps avoid unexpected authorization behavior.

8. Role-Based Authorization vs Policy-Based Authorization

Role-based authorization works well when permissions are closely connected to user roles.

However, some applications require more flexible access-control rules.

For example:

  • Only verified employees can access a feature.

  • Only managers with a specific permission can approve requests.

  • Users can update only records belonging to their department.

In these situations, policy-based or resource-based authorization may be more suitable.

Role-Based AuthorizationPolicy-Based Authorization
Checks user roles.Evaluates defined authorization rules.
Simple to configure.More flexible for complex requirements.
Uses [Authorize(Roles = "...")].Uses [Authorize(Policy = "...")].
Suitable for straightforward access control.Suitable for more detailed access requirements.

We introduced policy-based authorization in Day 15. It can be extended as application security requirements become more complex.

9. Best Practices for Role-Based Authorization

When implementing role-based authorization in a real-world application, consider the following practices.

1. Assign Roles Through a Trusted System

Users should not be able to assign themselves Admin or Manager roles through an ordinary registration or profile-update request.

2. Follow the Principle of Least Privilege

Give users only the permissions they need.

3. Protect Every Sensitive Endpoint

Do not assume that hiding a frontend button is enough. Always enforce authorization on the API.

4. Avoid Hardcoding Role Names Everywhere

Use constants or a centralized role-management approach to maintain consistency.

5. Validate JWT Tokens Properly

Validate the signature, issuer, audience, and expiration before trusting any claims.

6. Consider Resource-Based Authorization

A role alone may not be enough. For example, a Manager should not automatically have access to every team's records.

7. Plan for Role Changes

JWTs contain claims issued at a particular time. If a user's role changes, an already-issued token may continue to carry the old role until it expires or is otherwise invalidated. Consider short token lifetimes and appropriate revocation or revalidation strategies.

8. Test Both Allowed and Denied Access

Test users with different roles to ensure that protected endpoints behave as expected.

10. Key Takeaways

Let's quickly recap what we learned:

  • Role-Based Authorization controls API access based on user roles.

  • Roles represent categories of users, such as Admin, Manager, and Employee.

  • JWT tokens can carry role claims.

  • ClaimTypes.Role identifies a role claim in .NET.

  • [Authorize(Roles = "...")] restricts endpoints based on user roles.

  • Multiple roles in a single attribute allow access when at least one role matches.

  • Multiple authorization attributes introduce separate requirements.

  • Role-based authorization works alongside JWT authentication.

  • Resource-based and policy-based authorization can handle more complex access requirements.

Role-based authorization helps us build APIs where different users have different levels of access while keeping security rules on the server.

What's Next?

In Day 18, we'll explore CORS with Angular and .NET Web API.

We'll understand what Cross-Origin Resource Sharing (CORS) is, why browsers restrict cross-origin requests, and how to configure CORS policies in a .NET Web API application.

We'll also connect an Angular frontend with our .NET Web API and understand how to allow requests from trusted frontend origins.


Skills Tutorials – Learn • Build • Grow Together

Continue learning with our 30-day .NET Web API series.