How to Implement JWT Authentication in ASP.NET Core Minimal API
In today’s world of modern web applications, secure authentication is not just a feature – it’s a necessity. Token-based authentication, particularly using JWT (JSON Web Tokens), has become the go-to solution for securing APIs. Before we dive into the implementation, let’s understand why this matters.
What Is Token-Based Authentication?
Token-based authentication is a security mechanism that allows users to verify their identity and securely access API resources. Instead of sending credentials with each request, the user receives a token after initial authentication, which they can use for subsequent requests.
How Does It Work?
The process follows these key steps:
- Request Authorization
- The client sends user credentials (username/password) to the authentication server
- This happens only once during the initial login
- User Authentication
- The server validates the credentials
- This could be against a database, Active Directory, or other identity providers
- Token Generation
- Upon successful authentication, the server generates a JWT
- This token contains encoded user information and permissions
- The token is signed to ensure its integrity
- Token Usage
- The client stores the token (usually in memory or local storage)
- For each subsequent request, the client includes the token in the Authorization header
- Token Validation
- The API validates the token’s signature and expiration
- If valid, the requested resource is returned
- If invalid, the request is rejected with a 401 Unauthorized response
Why Use JWT in Modern Applications?
- Stateless: Servers don’t need to store session information
- Scalability: Works great with microservices and distributed systems
- Cross-domain: Easily share authentication across different domains
- Mobile-friendly: Perfect for mobile applications and SPAs
Understanding Authentication vs. Authorization
When developing secure applications, it’s crucial to distinguish between authentication and authorization. Though often used interchangeably, they represent different processes in the realm of security management.
What is Authentication?
Authentication is the process of verifying who a user is. It answers the question:
“Is this person who they claim to be?”
What is Authorization?
Authorization, on the other hand, determines what an authenticated user is allowed to do. It answers the question:
“What can this user access or perform?”
After authentication, the system grants permissions based on roles, rules, or policies that dictate access rights. Authorization can involve:
- Role-Based Access Control (RBAC): Users are assigned roles, and permissions are granted based on those roles.
- Attribute-Based Access Control (ABAC): Access rights are granted based on user attributes and environmental conditions.
Example: Once you’re logged in, authorization determines whether you can view user settings, access payment information, or perform administrative functions based on your role within the application.
Authentication and authorization in ASP.NET Core have similar implementation patterns.
- Authentication is managed by the
IAuthenticationService
and implemented through authentication middleware. - Authorization is handled by the
IAuthorizationService
and executed via authorization middleware.
In the authorization layer, there are two main strategies for determining user access to resources:
- Role-Based Strategies: Access is granted based on the user’s assigned role, such as “Administrator” or “User.”
- Claim-Based Strategies: Access is determined by claims issued by a central authority, allowing for more granular control over permissions.
Example: Securing an ASP.NET Core Minimal API with dotnet user-jwts
Modern web APIs need secure ways to authenticate and authorize users. In this chapter, we’ll use JWT (JSON Web Tokens) to secure an ASP.NET Core Minimal API. To simplify the token creation process during development, we’ll leverage the dotnet user-jwts
tool, a built-in .NET CLI tool for generating and managing development JWTs.
Disclaimer: This example is intended for educational purposes only. While it demonstrates key concepts, there are more efficient ways to write and optimize this code in real-world applications. Consider this a starting point for learning, and always aim to follow best practices and refine your implementation.
Prerequisites
Before starting, make sure you have the following installed:
- .NET SDK: Download and install the .NET SDK if you haven’t already.
- Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
- C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.
This example is intended for development and learning purposes only. While it demonstrates a simple JWT implementation in ASP.NET Core using dotnet user-jwts
, you should use a production-ready token generation system (e.g., IdentityServer, Azure AD, or another identity provider) in real-world applications.
Step 1: Create a Minimal API
dotnet new webapi -minimal -n SecureWeatherApi
cd SecureWeatherApi
Add the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Step 2: Add Authentication and Authorization
Configure the authentication and authorization in Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Configure Authentication and JWT Bearer.
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorizationBuilder();
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Here is an explanation of the added lines
Configures authentication services and adds JWT Bearer authentication handler:
builder.Services.AddAuthentication().AddJwtBearer();
Registers authorization services with a fluent builder interface:
builder.Services.AddAuthorizationBuilder();
Adds authentication and authorization middleware to the request pipeline:
app.UseAuthentication();
app.UseAuthorization();
Protects an endpoint requiring authentication:
app.MapGet("/weatherforecast", () =>
{
//...
})
.RequireAuthorization()
Step 3: Test the Protected Endpoint (Unauthenticated)
Go to the SecureWeatherApi.http file that is used to run the api and press Send Request, you should receive a 401 Unauthorized response:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
Date: Fri, 29 Nov 2024 15:17:03 GMT
Server: Kestrel
WWW-Authenticate: Bearer
Step 4: What is dotnet user-jwts?
The dotnet user-jwts
tool is designed to simplify JWT authentication during development. It allows you to:
- Create development tokens for testing secured APIs
- Configure JWT-related settings automatically
- Add metadata to generated tokens, such as audience and claims
Step 5: Create a JWT with dotnet user-jwts
Generate a development JWT token:
dotnet user-jwts create
The command will output a token:
New JWT saved with ID 'dedc8297'.
Name: ottorinobruni
Token: eyJhbGciOiJIUzI1NiIsInR5cC...
dotnet user-jwts automatically configures the Issuer, Audience, and signing key within a development JSON (appsettings.Development.json), avoiding the need for manual configurations for signed tokens.
{
"Authentication": {
"Schemes": {
"Bearer": {
"ValidAudiences": [
"http://localhost:10341",
"https://localhost:44324",
"http://localhost:5168",
"https://localhost:7137"
],
"ValidIssuer": "dotnet-user-jwts"
}
}
}
}
Step 6: Test the Protected Endpoint (Authenticated)
Go to the SecureWeatherApi.http file used to run the API and change the call by adding the Bearer Authorization with the token:
@SecureWeatherApi_HostAddress = http://localhost:5168
@Access_JWT = eyJhbGciOiJIU...
GET {{SecureWeatherApi_HostAddress}}/weatherforecast/
Accept: application/json
Authorization: Bearer {{Access_JWT}}
###
You should now receive the weather forecast data.
Conclusions: Understanding JWT Tokens and API Security in Modern Development
The use of JSON Web Tokens (JWT) plays an essential role in securing modern APIs by providing stateless authentication and versatile authorization mechanisms. Tools like dotnet user-jwts
make development simpler by handling the complexity of token generation and validation during development. Adopting JWT as part of your API security strategy can improve scalability, flexibility, and cross-platform compatibility, especially in distributed or microservice-based systems.
However, like any tool, it’s essential to evaluate your system requirements carefully before choosing JWT as your solution and to follow security best practices to maximize its effectiveness. In production environments, using production-grade identity providers (e.g., Azure AD, IdentityServer, or Auth0) ensures additional robustness and reliability.
By incorporating JWT into your development process, you build secure, scalable, and maintainable APIs that align with the needs of modern application architectures.
If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.
Thanks for reading!