First of all, you should never store any JWT tokens used for API authentication in the browser. Brower storage is in itself unsafe, as you or your app has no control over it. You might choose to store it as a cookie or in local storage in the browser but that introduces other issues. Cookies are are only accessible in Blazor during component initialization since it depends on HttpContext. When the component is fully rendered, HttpContext is null so you cannot retrieve cookies. You might think, I’ll just put it in locastorage, but this is only accesible after rendering, making it again unusable during application startup.
Luckily, there are options to store the token server side, as following
builder.Services.AddHttpClient().AddHttpMessageHandler<AuthTokenHandler>();
What this does is that when injecting the httpclient, the httpclient itself references another middleware which is fired before the httpclient fires. AuthTokenHandler can look like this.
public class AuthTokenHandler : DelegatingHandler
{
private readonly ITokenService _tokenService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserManager<ApplicationUser> _userManager;
public AuthTokenHandler(ITokenService tokenService, IHttpContextAccessor httpContextAccessor, UserManager<ApplicationUser> userManager)
{
_tokenService = tokenService;
_httpContextAccessor = httpContextAccessor;
_userManager = userManager;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var storedToken = await _tokenService.GetTokenAsync();
if (string.IsNullOrEmpty(storedToken))
{
var user = _httpContextAccessor.HttpContext?.User;
var userFromDb = await _userManager.FindByNameAsync(user.Identity.Name);
var roles = await _userManager.GetRolesAsync(userFromDb);
var newToken = await _tokenService.CreateJwtToken(userFromDb.Id, userFromDb.UserName, roles.ToArray(), string.Empty);
await _tokenService.SetTokenAsync(newToken);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken);
return await base.SendAsync(request, cancellationToken);
}
else
{
var isValid = await _tokenService.EnsureTokenValid(storedToken);
if (!isValid)
{
storedToken = await _tokenService.RefreshToken(storedToken);
await _tokenService.SetTokenAsync(storedToken);
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", storedToken);
return await base.SendAsync(request, cancellationToken);
}
}
}
When the app starts, storedToken is null so we can use _httpContextAccessor (which is available at initialization) to retrieve the user and generate the token. The token is generated and stored in the TokenService. When the token expires, the token is refreshed. Token storage should be done using IDistributedCache so a token can be stored for each user of your app.
The token refresh mechanism is done by storing the refreshtoken in the token itself as following:
public async Task<string> CreateJwtToken(string userId, string userName, string[] roles, string refreshToken)
{
var authSigningKey = Encoding.UTF8.GetBytes(_key);
if (string.IsNullOrEmpty(refreshToken))
{
refreshToken = await GenerateRefreshJwtToken();
}
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("Name", userName),
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim("RefreshToken", refreshToken),
}.Concat(roles.Select(role => new Claim(ClaimTypes.Role, role)))),
Expires = DateTime.UtcNow.AddMinutes(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(authSigningKey), SecurityAlgorithms.HmacSha256Signature),
Issuer = "<your_issuer>",
Audience = "<your_audience>"
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
This raises the obvious question, is this secure ? In short, yes :), since the token never reaches the client and cannot be extracted in any way by the client. You need to host the app using https, so the token is secure in transit.
This is a very simple and effective. As you can see, there are no JWT tokens stored on the client side

