How I Built a JWT Refresh Token Middleware in ASP.NET Core
- Get link
- X
- Other Apps
A few months ago, I was working on a project where our APIs were protected using JWT tokens. Everything was running smoothly… until we realized that our tokens were expiring too quickly. Users were getting logged out, and every client request needed re-authentication. That’s when I decided to implement a refresh token mechanism directly in middleware, and it turned out to be a neat solution.
Understanding the Problem
JWT tokens are stateless and self-contained, which is great for scalability, but the downside is that once they expire, the user needs a new token. Traditionally, developers handle this on the client side with a separate refresh endpoint. But I wanted a more transparent approach:
- Whenever a valid token was nearing expiration, the server would automatically issue a new token.
- This would reduce friction for the user.
- The new token would be added to the response headers so clients could pick it up without changing the response payload.
Step 1: Creating Middleware to Intercept Responses
I started by creating a custom JWT middleware in ASP.NET Core. The middleware intercepts the HTTP response, reads the existing JWT token, and generates a new one if needed:
public class JwtTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly AppSettings _appSettings;
private readonly ILogger<JwtTokenMiddleware> _logger;
public JwtTokenMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings, ILogger<JwtTokenMiddleware> logger)
{
_next = next;
_appSettings = appSettings.Value;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var originalBodyStream = context.Response.Body;
try
{
using var responseBodyStream = new MemoryStream();
context.Response.Body = responseBodyStream;
await _next(context);
await ProcessResponse(context, originalBodyStream);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in JWT Token Middleware");
context.Response.Body = originalBodyStream;
throw;
}
}
}
Step 2: Checking and Generating a New Token
Inside the middleware, I created a method to generate a new JWT token when the old one is valid:
private string generateJwtToken(string tokenData, Claim claimData)
{
ClaimsPrincipal principal;
if (_appSettings.JwtAlgorithm == "RS256" && !string.IsNullOrEmpty(_appSettings.RsaPublicKey))
{
principal = JwtHelper.ValidateRs256Token(tokenData, _appSettings.RsaPublicKey, _appSettings.Issuer, _appSettings.Audience);
}
else
{
principal = JwtHelper.ValidateHmacToken(tokenData, _appSettings.Secret, _appSettings.Issuer, _appSettings.Audience);
}
if (principal?.Identity is ClaimsIdentity claimsIdentity)
{
if (claimData != null && !claimsIdentity.Claims.Any(x => x.Type == claimData.Type))
claimsIdentity.AddClaim(claimData);
var expires = DateTime.UtcNow.AddMinutes(_appSettings.Tokentimout.Value);
if (_appSettings.JwtAlgorithm == "RS256" && !string.IsNullOrEmpty(_appSettings.RsaPrivateKey))
return JwtHelper.GenerateRs256Token(claimsIdentity, _appSettings.Issuer, _appSettings.Audience, expires, _appSettings.RsaPrivateKey);
else
return JwtHelper.GenerateHmacToken(claimsIdentity, _appSettings.Issuer, _appSettings.Audience, expires, _appSettings.Secret);
}
return "";
}
Step 3: Appending the Token to the Response
Finally, I added the new token to the response headers so clients could pick it up automatically:
private async Task<string> AppendTokenToResponse(HttpContext context, string responseBody)
{
var currentToken = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (!string.IsNullOrEmpty(currentToken))
{
var newToken = generateJwtToken(currentToken, null);
context.Response.Headers.Add("X-New-Token", newToken);
}
return responseBody;
}
Step 4: Benefits I Observed
- Users rarely experienced session expiration interruptions.
- No extra refresh endpoints were needed in controllers.
- The solution was stateless and scalable, perfect for microservices.
- RS256 made it secure even if multiple services needed to validate tokens.
Key Takeaways
- Middleware can automatically handle refresh tokens without changing API payloads.
- Always validate tokens first before generating new ones.
- Use RS256 for production APIs for strong security.
- Adding tokens in response headers is safer than modifying JSON responses directly.
This experience taught me that sometimes the simplest solutions come from intercepting the response, not building new endpoints. By leveraging ASP.NET Core middleware, we were able to enhance security, improve user experience, and simplify token management.
Pro tip: If you’re using Angular, React, or mobile clients, make sure to capture the X-New-Token header in every response and update your client-side JWT automatically. That way, your users never get logged out unexpectedly.
- Get link
- X
- Other Apps
Comments
Post a Comment