Hey everyone, welcome to our deep dive into JWTs in ASP.NET Core! Whether you’re a beginner or a seasoned developer, this article will guide you through the ins and outs of implementing secure authentication and authorization using JSON Web Tokens (JWT). Grab your favourite beverage, sit back, and let’s get started! 😊
What is JWT? 🔍
JWT stands for JSON Web Token. Think of it as a digital passport that securely transmits information between parties. It’s compact, URL-safe, and self-contained, meaning all the necessary data for authentication is right inside the token. A JWT consists of three key parts:
Header:
Specifies the token type and the hashing algorithm used (e.g., HMAC SHA256).Payload:
Contains the claims—statements about an entity (usually the user) such as their ID, email, and roles. These claims are essential for both authentication and authorization.Signature:
Ensures that the token hasn’t been tampered with. It’s created using the header, payload, and a secret key.
You can explore JWTs and see their inner workings at jwt.io 🔗.
How Does the Authentication & Authorization Process Work? 🛡️
When a client sends a request to a secure API, here’s what happens behind the scenes:
Client Request:
The client sends a request including the JWT in theAuthorization
header.-
Authentication:
- The ASP.NET authentication provider extracts the JWT.
- It validates the token’s signature, expiration, and issuer.
- If valid, the user is authenticated.
-
Authorization:
- The server checks if the user has the required claims to access the resource.
- If the claims match the required policy, access is granted; otherwise, a
403 Forbidden
error is returned.
Policy-Based vs. Role-Based Access Control 🔐
Role-Based Access Control (RBAC)
- RBAC assigns roles directly to users.
- For example, only users with an Admin role might access certain endpoints.
- This method can become unwieldy as your user base grows.
Policy-Based Access Control
-
Policy-Based access control defines policies (like
CanRead
) that are assigned to roles. - When a user’s role changes, their permissions update automatically without modifying each user individually.
- This approach is more flexible and scalable, especially for larger applications.
In our demo, we focus on policy-based access control for that extra bit of flexibility!
Implementing JWT in ASP.NET Core: A Step-by-Step Guide 💻
Let’s walk through how to set up JWT authentication in an ASP.NET Core application.
Clone the project
Clone the project from Github and take a look at the authentication and authorisation configuration in the DependencyInjection.cs
file:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
services.AddAuthorization();
services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();
We have all authentication configuration in the JwtBearerOptionsSetup
file:
internal class JwtBearerOptionsSetup(IOptions<JwtOptions> jwtOptions): IPostConfigureOptions<JwtBearerOptions>
{
private readonly JwtOptions _jwtOptions = jwtOptions.Value;
public void PostConfigure(string? name, JwtBearerOptions options)
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = _jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
}
}
This setup ensures that every token is validated against your issuer, audience, and signing key.
To add permission authorisation, we have added two files PermissionAuthorizationHandler
and PermissionAuthorizationPolicyProvider
.
In the PermissionAuthorizationHandler
we make sure that the request has enough permission:
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
HashSet<string> permissions = context
.User
.Claims
.Where(x => x.Type == Constants.Claims.Permissions)
.Select(x => x.Value)
.ToHashSet();
if (permissions.Contains(requirement.Permission))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Now, only requests with a valid JWT will be able to access these endpoints.
Another important file is JwtProvider
which is responsible for creating the JWT token:
public sealed class JwtProvider(IOptions<JwtOptions> jwtOptions, IPermissionService permissionService) : IJwtProvider
{
private readonly JwtOptions _jwtOptions = jwtOptions.Value;
public async Task<string> GenerateJwtTokenAsync(User user)
{
List<Claim> claims =
[
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new(JwtRegisteredClaimNames.Email, user.Email!),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
];
HashSet<string> permissions = await permissionService.GetPermissionsAsync(user.Id);
claims.AddRange(permissions.Select(permission => new Claim(Constants.Claims.Permissions, permission)));
SigningCredentials signingCredentials =
new(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)), SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new(_jwtOptions.Issuer, _jwtOptions.Audience, claims,
expires: DateTime.UtcNow.AddMinutes(_jwtOptions.TimeoutMinutes), signingCredentials: signingCredentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
That's all the configuration we need to know about JWT authentication and authorisation.
Wrap-Up & Key Takeaways 📝
Today, we explored:
JWT Fundamentals:
Understanding JWT as a secure, compact way to transfer claims between parties.Authentication & Authorization Flow:
From initial user login to token validation and access control.Policy-Based Access Control:
Offering flexibility over traditional role-based approaches by defining policies likeCanRead
that are associated with roles.Implementation in ASP.NET Core:
A step-by-step guide including configuration, creating a login endpoint, and securing your APIs with JWT.
Implementing JWT in your ASP.NET Core applications not only simplifies authentication but also scales beautifully with your growing user base. If you have any questions or feedback, feel free to drop a comment below!
Happy coding! 💻✨
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.