Introduction
- JWT (JSON Web Token) authentication is a widely used approach to secure Web APIs. However, validating tokens on every request can be performance-intensive, especially when handling thousands of concurrent users. To overcome this bottleneck, we can cache token data in an in-memory store like Redis, which offers fast access, scalability, and built-in expiry support.
- In this article, we’ll walk through how to implement JWT-based authentication in an ASP.NET Core Web API application and enhance it using Redis for token storage.
What You'll Build
- A secure login endpoint that issues JWT and refresh tokens.
- Redis-based token storage for quick validation.
- A clean and scalable architecture with separation of concerns.
Project Structure
| Component | Responsibility |
|---|---|
AuthController |
Handles login and token generation |
ITokenService |
Interface for token generation logic |
RedisManager |
Handles Redis token storage/retrieval |
TokenModel |
Represents token metadata |
Program.cs |
Configures DI, Redis, and middleware |
Setting Up Redis in Program.cs
Add the Redis connection to the DI container using StackExchange.Redis:
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
// Register Redis connection
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect("localhost:6379")); // Update if needed
// Register services
builder.Services.AddScoped<RedisManager>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthorization();
app.MapControllers();
app.Run();
AuthController – Handling Login & Token Issuance
When a user logs in, we generate the JWT and refresh token, store them in Redis, and return them:
public class AuthController(ITokenService tokenService, RedisManager redisManager) : Controller
{
private readonly ITokenService _tokenService = tokenService ?? throw new ArgumentNullException(nameof(tokenService));
private readonly RedisManager _redisManager = redisManager ?? throw new ArgumentNullException(nameof(redisManager));
[AllowAnonymous]
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Post([FromBody] UserProfile value)
{
try
{
var authClaims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, value.LoginId),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new(ClaimTypes.Role, "Admin")
};
var l_refreshToken = _tokenService.GenerateRefreshToken();
var l_jwtToken = _tokenService.GenerateAccessToken(authClaims);
var data = new TokenModel
{
AccessToken = l_jwtToken,
RefreshToken = l_refreshToken,
UserId = value.LoginId,
AccessTokenExpiry = DateTime.Now.AddMinutes(15),
RefreshTokenExpiry = DateTime.Now.AddHours(8),
LastUpdate = DateTime.Now
};
await _redisManager.StoreTokenAsync(data);
return Ok(new
{
userId = value.LoginId,
accessToken = data.AccessToken,
refreshToken = data.RefreshToken,
});
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
Join the conversation! Your thoughts help the community grow.