Implementing JWT Authentication with Refresh Tokens in ASP.NET Core Web API


Securing your APIs is more important than ever. In this guide, you’ll learn how to implement JWT Authentication and Refresh Tokens in a ASP.NET Core Web API, a must-know skill for any backend developer working with .NET technologies.
Whether you're building with ASP.NET Core MVC, Web API, or developing enterprise-level systems, understanding JWT Authentication is essential to building secure, scalable applications.
Let’s walk through how to code secure endpoints using ASP.NET Core JWT token authentication, complete with refresh token support.
🔐 Why Use JWT with Refresh Tokens?
In ASP.NET Core JWT Authentication, tokens allow stateless authentication. That means your API doesn’t need to store session data, which improves performance and scalability.
However, JWT tokens have expiration times. That’s why we implement Refresh Token JWT functionality. So users don’t need to log in repeatedly when their access token expires.
Using ASP.NET Core JWT Refresh Token implementation ensures a smooth and secure user experience.
🛠 Setting Up ASP.NET Core for JWT
To start using ASP.NET Core JWT, install these essential libraries:
System.IdentityModel.Tokens.Jwt
Microsoft.AspNetCore.Authentication.JwtBearer
This demo uses SQL Server and a well-structured repository pattern for handling user and token data.
Whether you're working with ASP.NET Core MVC JWT token authentication or a lightweight ASP.NET Core API JWT service, these steps apply universally.
🧱 Project Structure Overview
Entity Classes:
User
,RefreshToken
Repositories: Encapsulate CRUD operations
DbContext: Includes
DbSet<User>
andDbSet<RefreshToken>
Admin Seeding: An initial user is seeded for testing
🔧 Configure JWT Token Authentication
In appsettings.json
, define your token configuration:
"JwtConfig": {
"Issuer": "http://localhost:5280/",
"Audience": "http://localhost:5280/",
"Key": "UhdMzMEY0l1pv5oRMLQhSLJa2Bh0qVDFjmKxe6mGsbEJoQkHiW5Qjd8DzFT7IX7kQ4WHfcLB0GhER448I0FzNsAaygJEsKfny4rw",
"TokenValidityMins": 10,
"RefreshTokenValidityMins": 30
}
In Program.cs
, configure ASP.NET Core JWT token authentication like this:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["JwtConfig:Issuer"],
ValidAudience = builder.Configuration["JwtConfig:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtConfig:Key"]!)),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
This setup works whether you’re building a Dotnet Core JWT Authentication service or a large-scale dotnet system.
Don't forget to include Authentication and Authorization middleware in the pipeline.
app.UseAuthentication();
app.UseAuthorization();
👤 Coding the Login Flow
You'll need three models:
LoginRequest
(Username, Password)LoginResponse
(JWT Token, Refresh Token, Expiry)RefreshResponse
(Token)
public class LoginRequestModel
{
public string? Username { get; set; }
public string? Password { get; set; }
}
public class LoginResponseModel
{
public string? UserName { get; set; }
public string? AccessToken { get; set; }
public int ExpiresIn { get; set; }
public string? RefreshToken { get; set; }
}
public class RefreshRequestModel
{
public string? Token { get; set; }
}
The JWT token is generated using JwtSecurityTokenHandler
. You’ll include claims like username and sign it using the configured secret key.
With DotNet core JWT, make sure passwords are hashed properly. A custom PasswordHandler
helps keep your users’ credentials secure.
🔄 Implementing Refresh Token Logic
Here’s how to build a Refresh Token system in your .NET Web API:
Generate a GUID-based refresh token
Save it in the database with an expiry time
During login, generate and return both JWT and refresh token
Create a method to validate the refresh token
If valid, issue a new JWT and a new refresh token
This process is standard in modern ASP.Net Core API JWT Authentication implementations.
public class JwtAuthenticationService
{
private readonly IConfiguration _configuration;
private readonly UserRepository _userRepository;
private readonly RefreshTokenRepository _refreshTokenRepository;
public JwtAuthenticationService(IConfiguration configuration,
UserRepository userRepository,
RefreshTokenRepository refreshTokenRepository)
{
_configuration = configuration;
_userRepository = userRepository;
_refreshTokenRepository = refreshTokenRepository;
}
public async Task<LoginResponseModel?> Authenticate(LoginRequestModel request)
{
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
return null;
var user = await _userRepository.Get(request.Username);
if (user is null || !PasswordHashHandler.VerifyPassword(request.Password, user.PasswordHash!))
return null;
return await GenerateJwtToken(user);
}
public async Task<LoginResponseModel?> ValidateRefreshToken(string token)
{
var refreshToken = await _refreshTokenRepository.Get(token);
if (refreshToken is null || refreshToken.Expiry < DateTime.UtcNow)
return null;
await _refreshTokenRepository.Delete(refreshToken);
var user = await _userRepository.Get(refreshToken.UserId);
if(user is null) return null;
return await GenerateJwtToken(user);
}
private async Task<LoginResponseModel> GenerateJwtToken(User user)
{
var issuer = _configuration["JwtConfig:Issuer"];
var audience = _configuration["JwtConfig:Audience"];
var key = Encoding.ASCII.GetBytes(_configuration["JwtConfig:Key"]!);
var tokenValidityMins = _configuration.GetValue<int>("JwtConfig:TokenValidityMins");
var tokenExpiryTimeStamp = DateTime.UtcNow.AddMinutes(tokenValidityMins);
var token = new JwtSecurityToken(issuer,
audience,
[
new Claim(JwtRegisteredClaimNames.Name, user.Username!)
],
expires: tokenExpiryTimeStamp,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature));
var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
return new LoginResponseModel
{
UserName = user.Username,
AccessToken = accessToken,
ExpiresIn = (int)tokenExpiryTimeStamp.Subtract(DateTime.UtcNow).TotalSeconds,
RefreshToken = await GenerateRefreshToken(user.Id)
};
}
private async Task<string> GenerateRefreshToken(int userId)
{
var refreshTokenValidityMins = _configuration.GetValue<int>("JwtConfig:RefreshTokenValidityMins");
var refreshToken = new RefreshToken
{
Token = Guid.NewGuid().ToString(),
Expiry = DateTime.UtcNow.AddMinutes(refreshTokenValidityMins),
UserId = userId
};
await _refreshTokenRepository.Create(refreshToken);
return refreshToken.Token;
}
}
⚙ Background Cleanup for Expired Tokens
To avoid database bloat, expired refresh tokens should be cleaned up periodically.
For this demo, a Background Service is implemented that:
Runs every hour
Deletes all expired tokens
✨ You can use alternatives like Hangfire, Quartz.NET, or SQL Agent for more advanced scheduling.
🧪 Testing with Postman & Swagger
Use Postman to authenticate and retrieve tokens.
Test the refresh endpoint by passing the refresh token.
Swagger is also configured to support JWT authentication.
Once authorized with a token in Swagger, you can test all secured endpoints directly.
🧠 Summary
Here’s what you’ve learned:
How to Code secure APIs using ASP.NET Core JWT Token
Why and how to implement a Refresh Token JWT mechanism
How to manage token expiration using Dotnet Core JWT Refresh Token
Best practices for .NET API Authentication
How to integrate it all in your Web API with Swagger
Whether you’re just starting out or leveling up your coding skills, understanding JWT Authentication is a game-changer.
Subscribe to my newsletter
Read articles from Coding Droplets directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
