Reputation: 4592
I use JWT tokens in my ASP.NET Core 2 Web API project
My front end web client sends A JWT token it obtains initially from the Web API login API with every request which works correctly. However I also have a offline client which uses the same API and initially logs in online and then stores the JWT offline sending it only when the user syncs the app which could be a week later. The token could have expired or the server web app restarted in the meant time.
If the JWT token fails due to being expired for the offline client I still want the Web API controller method to be hit and the ASP.NET Identity User
object populated as I need the user name. I will log this event but I need the username from the expired/invalid token as well. Currently the controller method won't get entered if authentication fails so I add an [AllowAnonymous] attribute however this will cause the User.Identity to be null where as I want the username
My code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = _config["Tokens:Issuer"],
ValidAudience = _config["Tokens:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]))
};
});
When a user logs in
private IActionResult CreateToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_config["Tokens:Issuer"],
_config["Tokens:Audience"],
claims.ToArray(),
expires: DateTime.Now.AddMinutes(60),
signingCredentials: creds);
var results = new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
};
return Created("", results);
I make sure after authenticated the various API calls pass the JWT token.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class MyController : Controller
{
This allows me to capture the logged in user with the following code for the various API calls
var loggedInUser = User.Identity.Name;
How do I read the username from the JWT token if it has expired or is invalid?
Upvotes: 6
Views: 4951
Reputation: 305
i had another workaround for getting the token if it was expired without setting `ValidateLifetime' to false (.net 7 with Nullable enabled)
you just read the token from the header (i assume the user will send it anyway expired or not) then decode it with something like
private Task<JwtSecurityToken?> GetJwtSecurityTokenFromHeader()
{
try
{
var accessToken = HttpContext.Request.Headers[HeaderNames.Authorization];
string tokenString = accessToken.ToString();
if (tokenString.StartsWith("bearer", StringComparison.InvariantCultureIgnoreCase))
{
tokenString = tokenString.Substring(7);
}
var handler = new JwtSecurityTokenHandler();
return Task.FromResult((JwtSecurityToken?)handler.ReadJwtToken(tokenString));
}
catch
{
return Task.FromResult(default(JwtSecurityToken?));
}
}
if the user didn't send the Authorization header in the request or sent random string it will return null otherwise you will get the token data you need
you can use it like this
var jwtSecurityToken = await GetJwtSecurityTokenFromHeader();
if (jwtSecurityToken is null)
{
// return or do something like send bad request
return;
}
var issuedAt = jwtSecurityToken.IssuedAt;
var name = jwtSecurityToken.Claims.First(x => x.Type == "name").Value;
Upvotes: 1
Reputation: 1212
If you want Authorization to go through even if token has expired, you can simply disable lifetime validation by adding it to TokenValidationParameters
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = _config["Tokens:Issuer"],
ValidAudience = _config["Tokens:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]))
//Do not check the expiry of token
ValidateLifetime = false,
};
});
This will ensure that tokens are considered valid by the Authorization middleware and it populates the User.Identity
object in the MVC middleware.
However, this check will disable expiry check for your web client as well. You will have to determine the appropriate client (cannot be definitely done) and check for expiry in the controllers.
A better way would be look at a better design to solve your basic need.
The token could have expired or the server web app restarted in the meant time
I am a bit confused by this statement. If the server web app restarted in the mean-time and you are encountering token expiry, I am assuming that you are using temporary signing key to sign the JWT tokens. You should switch to use of a permanent signing key.
For the offline client, where you want a long lived token, take a look at refresh tokens. The refresh tokens, typically, have a longer lifetime and can be used to exchange themselves for a newer access token. Standard oauth2.0 implementation issue refresh token when the scope offline_access
is specified. The name of scope should indicate the common use case for the refresh token.
You could have web client and offline client configured as two different clients on your Authority Server and thus ensure that only offline client is issued the refresh token.
Upvotes: 2