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.
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);
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;
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()
{
.
.
.
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"]
};
});
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);
}
If you'd like to see and try a very simplified sample of RBAC implemented via JWTs in .NET, check out this project:
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" }
-
Response Example:
{ "userName": "adityaoberai1" "name": "Aditya", "role": "Everyone
…
-
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)