Securing APIs with JWT in ASP.NET Core: A Detailed Guide
Securing APIs is a crucial aspect of modern web development, ensuring that only authorized users can access sensitive data. JSON Web Tokens (JWT) are a widely-used method to secure APIs due to their simplicity, standardization, and effectiveness. In this detailed guide, we will walk through the process of securing APIs using JWT in ASP.NET Core. This guide assumes you have a basic understanding of ASP.NET Core, C#, and RESTful APIs.
Step 1: Setting Up the ASP.NET Core Project
First, create a new ASP.NET Core Web API project using Visual Studio or the .NET CLI.
Using Visual Studio:
- Open Visual Studio and select "Create a new project."
- Choose "ASP.NET Core Web API" as the project type.
- Configure your project by providing a name, location, and solution name.
- Select the target framework (e.g., .NET 6.0).
- Click "Create."
Using .NET CLI: Open your terminal or command prompt and run the following command:
dotnet new webapi -n JWTAuthApi
cd JWTAuthApi
Step 2: Configuring JWT in appsettings.json
JWT requires some configuration settings to work. Open the appsettings.json
file and add the following configuration:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"JwtSettings": {
"SecretKey": "your_secret_key_here",
"Issuer": "your_issuer_name_here",
"Audience": "your_audience_name_here"
}
}
Make sure to create a strong, unique SecretKey
that is at least 32 characters long. The Issuer
and Audience
values can be any string that identifies your application.
Step 3: Creating the JWT Authentication Service
Create a new folder named Services
in your project and add a new class AuthService.cs
within this folder. This service will be responsible for generating JWT tokens.
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace JWTAuthApi.Services
{
public class AuthService
{
private readonly IConfiguration _configuration;
public AuthService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateJwtToken(string userId, string role)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:SecretKey"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Role, role)
};
var token = new JwtSecurityToken(
issuer: _configuration["JwtSettings:Issuer"],
audience: _configuration["JwtSettings:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
Step 4: Registering the JWT Authentication Service
Open the Startup.cs
or Program.cs
file (depending on your ASP.NET Core version), and register the AuthService
and configure JWT authentication.
For ASP.NET Core 3.1:
In Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Add AuthService
services.AddSingleton<AuthService>();
// Configure JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtSettings:Issuer"],
ValidAudience = Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSettings:SecretKey"]))
};
});
// Add authorization
services.AddAuthorization();
}
For ASP.NET Core 6.0 (Program.cs):
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Add AuthService
builder.Services.AddSingleton<AuthService>();
// Configure JWT Authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidAudience = builder.Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:SecretKey"]))
};
});
// Add authorization
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 5: Creating the Authentication Controller
Create a new folder named Controllers
in your project if it doesn't already exist. Add a new controller named AuthController.cs
within this folder. This controller will handle user authentication and provide JWT tokens.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace JWTAuthApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly AuthService _authService;
public AuthController(AuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
public IActionResult Login([FromBody] UserLoginModel user)
{
// Perform user validation here (e.g., check username and password in the database)
if (user.Username == "user" && user.Password == "password")
{
var token = _authService.GenerateJwtToken(user.Username, "Admin");
return Ok(new { token });
}
return Unauthorized();
}
}
public class UserLoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}
Note: This example uses hardcoded credentials for demonstration purposes. In a real application, you should validate the user's credentials against a database.
Step 6: Protecting APIs with JWT
To protect your APIs with JWT, apply the [Authorize]
attribute to the controllers or actions you want to secure. For example, to protect all actions in a controller, add the [Authorize]
attribute to the controller class.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JWTAuthApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is a protected resource");
}
}
}
You can also specify the required roles or policies for authorization:
[Authorize(Roles = "Admin")]
[HttpGet("admin")]
public IActionResult GetAdminData()
{
return Ok("This is an admin-only resource");
}
Step 7: Testing the Protected API
To test the protected API, first, obtain a JWT token by sending a POST request to the login
endpoint. You can use tools like Postman to easily test your API.
Request:
POST /api/auth/login
Content-Type: application/json
{
"username": "user",
"password": "password"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Use the obtained token to access the protected resource by including it in the Authorization
header with the Bearer
scheme.
Request:
GET /api/values
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response:
"This is a protected resource"
If you try to access the protected resource without a valid token, you will receive a 401 Unauthorized response.
Step 8: Handling Token Expiration and Refresh Tokens
JWT tokens typically have an expiration time (e.g., 30 minutes). Once the token expires, the user needs to obtain a new token by re-authenticating. Implementing token refresh is a common practice to enhance user experience and security.
This involves creating a new endpoint for refreshing tokens and issuing new tokens when the existing token is about to expire. Implementing refresh tokens involves securely storing and validating refresh tokens on the server, which can be more complex.
For simplicity, this guide does not cover token refresh, but understanding the basics of token expiration and handling it can help you design a secure and efficient authentication flow.
Conclusion
Securing APIs with JWT in ASP.NET Core involves configuring JWT settings, creating an authentication service to generate tokens, protecting your APIs using the [Authorize]
attribute, and testing the secured API. JWT provides a robust, flexible, and scalable solution for API security, making it a popular choice among developers.
By following this detailed guide, you should now have a good understanding of how to implement JWT-based authentication and authorization in ASP.NET Core. As you gain more experience, you can explore advanced topics such as token refresh, role-based authorization, and custom JWT claims.