In the previous article, we learned how ASP.NET Core stores user information as Claims after validating a JWT. We also saw how to access the current user's details using HttpContext.User.
But knowing who the user is is only half of the authentication process.
The next question is:
What is this user allowed to do?
For example:
A customer should be able to place an order.
An administrator should be able to manage products.
A manager should be able to view sales reports.
This is where Role-Based Authorization comes in.
In this article, you'll learn how roles work in ASP.NET Core, how to include them in a JWT, and how to protect API endpoints based on user roles.
![Implementing Role-Based Authorization in ASP.NET Core Web API Using JWT]()
What Is Role-Based Authorization?
Role-Based Authorization is a way of controlling access based on the user's role.
Instead of giving every authenticated user the same permissions, the application checks the user's assigned role before allowing access to an endpoint.
For example:
| User | Role |
|---|
| John | Customer |
| Sarah | Admin |
| David | Manager |
Although all three users are authenticated, they should not have access to the same resources.
Why Do We Need Roles?
Let's continue with our SecureShop API.
Suppose we have the following endpoints.
| Endpoint | Who Can Access? |
|---|
| GET /api/products | Everyone |
| POST /api/orders | Customer |
| POST /api/products | Admin |
| DELETE /api/products/{id} | Admin |
| GET /api/reports | Manager |
Without roles, every authenticated user would have identical permissions.
That would allow customers to delete products or administrators to perform actions they shouldn't.
Roles help prevent unauthorized access.
How Roles Work
The role is stored as a Claim inside the JWT.
For example:
User Id : 101
Name : John
Role : Customer
When ASP.NET Core validates the JWT, it automatically creates a user identity containing all claims, including the role.
Later, when a protected endpoint requires a specific role, ASP.NET Core compares the required role with the role stored in the authenticated user's claims.
Adding the Role Claim
In Session 4, we generated the JWT.
One of the claims looked like this:
new Claim(ClaimTypes.Role, user.Role)
If the user's role is:
Admin
Then the generated JWT contains:
Role → Admin
Similarly,
Customer
or
Manager
becomes part of the JWT.
This role travels with every authenticated request.
Protecting an Endpoint by Role
ASP.NET Core allows you to specify which role can access an endpoint.
Example:
[Authorize(Roles = "Admin")]
[HttpPost]
public IActionResult AddProduct()
{
return Ok("Product Added");
}
Now only users whose JWT contains:
Role = Admin
can access this endpoint.
If any other user calls this API, access is denied.
Allowing Multiple Roles
Sometimes more than one role should access the same endpoint.
For example, both Managers and Admins can view reports.
[Authorize(Roles = "Admin,Manager")]
[HttpGet]
public IActionResult GetSalesReport()
{
return Ok();
}
ASP.NET Core treats this as an OR condition.
The request succeeds if the user belongs to either role.
Applying Roles to an Entire Controller
Instead of protecting individual actions, you can protect the whole controller.
[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/admin")]
public class AdminController : ControllerBase
{
[HttpGet("dashboard")]
public IActionResult Dashboard()
{
return Ok();
}
[HttpPost("product")]
public IActionResult AddProduct()
{
return Ok();
}
}
Now every action inside the controller requires the Admin role.
This keeps your code cleaner and avoids repeating the same attribute.
Internal Authorization Flow
Let's see what happens when a request reaches a role-protected endpoint.
Client
│
│ JWT
▼
Authentication Middleware
│
▼
Validate JWT
│
▼
Create ClaimsPrincipal
│
▼
[Authorize(Roles="Admin")]
│
▼
Does User Have Admin Role?
│
Yes │ No
│
▼
Controller Executes
Notice that the role check happens after authentication.
A user must first be authenticated before ASP.NET Core checks their role.
Practical Example
Let's continue with the SecureShop API.
Suppose two users log in.
Customer
Name : John
Role : Customer
Admin
Name : Sarah
Role : Admin
Now both users call:
POST /api/products
The endpoint is protected like this:
[Authorize(Roles = "Admin")]
John's Request
John is authenticated.
↓
Role = Customer.
↓
Required Role = Admin.
↓
ASP.NET Core rejects the request.
403 Forbidden
Sarah's Request
Sarah is authenticated.
↓
Role = Admin.
↓
Required Role = Admin.
↓
The controller executes successfully.
200 OK
Understanding 401 vs 403
Many beginners confuse these two responses.
They have different meanings.
| Status Code | Meaning |
|---|
| 401 Unauthorized | The user is not authenticated. |
| 403 Forbidden | The user is authenticated but does not have permission. |
Example:
Understanding this difference makes debugging authentication and authorization issues much easier.
Common Mistakes
Mistake 1: Forgetting to Add the Role Claim
If the JWT doesn't contain a role claim, ASP.NET Core cannot perform role-based authorization.
Always include the role while generating the token.
Mistake 2: Confusing Authentication with Authorization
Authentication answers:
Authorization answers:
Both are required to secure an application.
Mistake 3: Using Roles for Every Permission
Roles work well for broad permissions such as:
However, they become difficult to manage when permissions become very detailed.
For fine-grained access control, ASP.NET Core provides Policy-Based Authorization, which we'll cover in a later article.
Mistake 4: Expecting Roles to Update Automatically
A JWT contains the user's role at the time the token is generated.
If a user's role changes in the database, existing tokens continue to use the old role until a new JWT is issued.
Key Takeaways
Role-Based Authorization controls what authenticated users are allowed to access.
The user's role is stored as a claim inside the JWT.
Use Authorize(Roles = "...") to restrict access to specific roles.
Multiple roles can be specified in a single attribute.
401 Unauthorized means the user is not authenticated.
403 Forbidden means the user is authenticated but does not have permission.
Summary
Role-Based Authorization builds on authentication by determining what an authenticated user is allowed to do. In ASP.NET Core, roles are stored as claims inside the JWT and are automatically available after the token is validated. By using the Authorize(Roles = "...") attribute, you can secure controllers and endpoints for specific user roles, support multiple roles, and clearly distinguish between authentication (401 Unauthorized) and authorization (403 Forbidden). Our SecureShop API can now identify users and restrict endpoints based on their roles. In the next article, we'll move beyond roles and explore Policy-Based Authorization, which provides a more flexible way to implement complex business rules such as age restrictions, membership levels, or custom access requirements.