# 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.

If you would rather follow along with the complete implementation, the full JWT auth and refresh-token setup - signing, rotation, and revocation - is on [Patreon](https://www.patreon.com/CodingDroplets), ready to run. [Chapter 7 of the ASP.NET Core Web API: Zero to Production course](https://aspnetcoreapi.codingdroplets.com/) builds exactly this inside a real API.

[![ASP.NET Core Web API: Zero to Production](https://newsletter.codingdroplets.com/images/aspnet-core-api-course-banner-1.jpg)](https://aspnetcoreapi.codingdroplets.com/)

%[https://www.youtube.com/watch?v=vTXXdm44IdQ] 

## 🔐 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.

> 💡 **A quick note before we dive in:** JWT with refresh tokens is one piece of a larger puzzle. Getting the implementation right in isolation is one thing - knowing how it fits alongside Clean Architecture, global error handling, rate limiting, and health checks in a real production API is another. If you want to see the complete picture, Chapter 7 of the [ASP.NET Core Web API: Zero to Production](https://aspnetcoreapi.codingdroplets.com/) course covers this exact pattern end-to-end, including token rotation, revocation strategies, and secure storage - all as part of a working codebase you can download and run.

## 🛠 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>` and `DbSet<RefreshToken>`
    
* **Admin Seeding**: An initial user is seeded for testing
    

## 🔧 Configure JWT Token Authentication

In `appsettings.json`, define your token configuration:

```json
  "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:

```csharp
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.

```csharp
app.UseAuthentication();
app.UseAuthorization();
```

## 👤 Coding the Login Flow

You'll need three models:

* `LoginRequest` (Username, Password)
    
* `LoginResponse` (JWT Token, Refresh Token, Expiry)
    
* `RefreshResponse` (Token)
    

```csharp
public class LoginRequestModel
{
    public string? Username { get; set; }
    public string? Password { get; set; }
}
```

```csharp
public class LoginResponseModel
{
    public string? UserName { get; set; }
    public string? AccessToken { get; set; }
    public int ExpiresIn { get; set; }
    public string? RefreshToken { get; set; }
}
```

```csharp
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.

```csharp
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](%22http://Quartz.NET%22), or SQL Agent for more advanced scheduling.

The complete source code and implementation guide are available to [Coding Droplets members on Patreon](https://www.patreon.com/CodingDroplets).

## 🧪 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.

## FAQ

**How long should a JWT access token live?** Keep access tokens short-lived - around 15 minutes is common. A short lifetime limits the damage if a token leaks, and the refresh token handles seamless renewal so users are not forced to log in repeatedly.

**What is refresh token rotation and why does it matter?** Rotation issues a brand-new refresh token every time one is used and invalidates the old one. If an attacker replays a stolen refresh token, the reuse is detected and the whole token family can be revoked - which is far safer than a long-lived, reusable refresh token.

**Where should I store refresh tokens?** Store them server-side (for example in your database) so they can be revoked, and deliver them to clients in a secure, HttpOnly cookie where possible. Never put long-lived tokens in localStorage, where any XSS can read them.

**How do I revoke a JWT before it expires?** Access tokens are stateless, so you cannot revoke an individual one directly - keep them short-lived instead. For immediate revocation, invalidate the associated refresh token in your store and, for high-security needs, maintain a small deny-list keyed by token id until the access token expires.

* * *

## About the Author

**Celin Daniel** is Co-founder of Coding Droplets with 13+ years of hands-on experience building, shipping, and operating .NET and ASP.NET Core systems in production. The guidance here comes from real projects and production incidents, not theory.

- Website: [codingdroplets.com](https://codingdroplets.com/)
- GitHub: [github.com/codingdroplets](http://github.com/codingdroplets/)
- YouTube: [youtube.com/@CodingDroplets](https://www.youtube.com/@CodingDroplets)
