I need to check if my JwtSecurityToken is expired or not.
I'm using System.IdentityModel.Tokens.Jwt library.
When I create a JwtSecurityToken like this:
var token = new JwtSecurityToken(
issuer: token_issuer,
audience: token_audience,
claims: claims,
expires: DateTime.Now.AddMinutes(15),
signingCredentials: creds
);
And check its lifetime, I'm getting 2 hours after the current time.
I check the lifetime this way (only for test purposes):
var lifeTime = new JwtSecurityTokenHandler().ReadToken(token).ValidTo;
And my method for validation:
private static bool ValidateToken(string token)
{
try
{
TokenValidationParameters validationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(token_salt),
ValidAudience = token_audience,
ValidIssuer = token_issuer,
RequireExpirationTime = true
};
var lifeTime = new JwtSecurityTokenHandler().ReadToken(token).ValidTo;
ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken(token_last, validationParameters, out SecurityToken validatedToken);
return true;
}
catch(Exception ex)
{
}
return false;
}
Can anyone explain what's happening or am I doing some wrong?
EDIT (for explanation purposes)
Test 1
Current time of my device: 10:06
Using expiration = DateTime.Now.AddSeconds(5);
Token's lifetime = 12:06:10 and Kind = UTC
Validation is ok.
Test 2
Current time of my device: 10:16
Using expiration = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc).AddSeconds(5);
Token's lifetime = 10:16:12 and Kind = UTC
Validation fails:
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired. ValidTo: '12/11/2017 10:16:12' Current time: '12/11/2017 12:18:40'.
Test 3
Using expiration = DateTime.UtcNow.AddSeconds(5);
Token's lifetime = 13:07:10 and Kind = UTC
Validation is ok.
But WHY the validation passes if when I run the validation was 13:12 and token's lifetime is 13:07? Is there a minimum time to bet set in expiration?
From the only source of this library got from google search you can see that the time is being converted to universal time (UTC).
DateTime.Now.AddMinutes(15) seems to be the problem.
Replacing it with Datetime.UtcNow.AddMinutes(15) might fix it
is your local time zone is UTC+2:00?
I know, that this is an old question, but TokenValidationParameters class has property called ClockSkew, which is set by default to 5 minutes. It might be your problem, as it was mine. Just set this property to for example to 1 second - ClockSkew = TimeSpan.FromSeconds(1).
It is not wrong time, in fact you create the expiration time based on your local time, but it looks for Utc time. so the solution is using Utc time in serverside mthod to generate expiratoin time and then in client side check the expiration token time by UtcNow.
Server :
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
expires: DateTime.UtcNow.AddMinutes(tokenValidityInMinutes),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
Client :
var identity = string.IsNullOrEmpty(tokenDTO?.Token) || tokenDTO?.Expiration < DateTime.UtcNow
Related
I am currently using the JwtSecurityToken class in System.IdentityModels.Tokens namespace. I create a token using the following:
DateTime expires = DateTime.UtcNow.AddSeconds(10);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var genericIdentity = new System.Security.Principal.GenericIdentity(username, "TokenAuth");
ClaimsIdentity identity = new ClaimsIdentity(claims);
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
var signingCreds = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.HmacSha256Signature);
var securityToken = handler.CreateToken(
issuer: issuer,
audience: ConfigurationManager.AppSettings["UiUrl"].ToString(),
signingCredentials: signingCreds,
subject: identity,
expires: expires,
notBefore: DateTime.UtcNow
);
return handler.WriteToken(securityToken);
For some reason even though the expires is set to 10 seconds after the current time it doesn't actually throw an exception when the token is being validated until about 5 minutes. After seeing this, I thought maybe there was a minimum expire time of 5 minutes, so I set the expire time to:
DateTime.UtcNow.AddMinutes(5);
Then it expires at 10 minutes, but the exception message says that the expire time is set to what it is supposed to be (5 minutes after the user logs in), and when it shows the current time in the exception it is 5 minutes after the expire time. So, it seems to know when it SHOULD expire, but it doesn't actually throw the exception until 5 minutes after the expire time. Then, since the token seems to be adding 5 minutes to whatever time I set it to expire I set the expire time to:
DateTime.UtcNow.AddMinutes(-5).AddSecond(10);
I tested this and so far it still hasn't expired (After more than ten minutes). Can someone please explain why this is happening and what I am doing wrong? Also, if you see anything else with the code I provided any guidance would be appreciated since I am new to using JWTs and this library.
The problem is related ClockSkew. Normally, the validation libraries (at least the MS one) compensate for clock skew. ClockSkew default value is 5 minutes. See some answer here
You can change ClockSkew in TokenValidationParameters:
var tokenValidationParameters = new TokenValidationParameters
{
//...your setting
// set ClockSkew is zero
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
After reading through #Denis Kucherov's answer, I found out that I could use the same custom validator he posted without using the JwtBearerOptions class which would have required me to add a new library.
Also, Since there are two namespaces which contain a lot of these same classes I will make sure to mention that all of these are using the System.IdentityModels... namespaces. (NOT Microsoft.IdentityModels...)
Below is the code I ended up using:
private bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters #param)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
private JwtSecurityToken ValidateJwtToken(string tokenString)
{
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
TokenValidationParameters validation = new TokenValidationParameters()
{
ValidAudience = "MyAudience",
ValidIssuer = "MyIssuer",
ValidateIssuer = true,
ValidateLifetime = true,
LifetimeValidator = CustomLifetimeValidator,
RequireExpirationTime = true,
IssuerSigningKey = securityKey,
ValidateIssuerSigningKey = true,
};
SecurityToken token;
ClaimsPrincipal principal = handler.ValidateToken(tokenString, validation, out token);
return (JwtSecurityToken)token;
}
There are seems to be some issue with LifeTimeValidator. You can just override its logic with a custom delegate. Also, use JwtBearerOptions class to control authentication middleware behavior. For example:
new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuer = _configuration["Tokens:Issuer"],
ValidAudience = _configuration["Tokens:Audience"],
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
LifetimeValidator = LifetimeValidator,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"]))
}
}
And assign LifetimeValidotor delegate, to provide its own timeout validation logic:
private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters #params)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
.NET Core Update
This is handled slightly differently in .NET Core, as the TokenValidationParameters are set in Startup.cs using the ConfigureServices() method and then handled automatically by the middleware.
Also note that the older InMemorySymmetricSecurityKey for signing the secret is now deprecated in favor of SymmetricSecurityKey, which is shown below.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _config.AuthenticationSettings.TokenAuthority,
ValidAudience = _config.AuthenticationSettings.TokenAuthority,
LifetimeValidator = TokenLifetimeValidator.Validate,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_config.AuthenticationSettings.SecurityKey))
};
});
// ...
}
And so I also made my own version of the token validator in #tkd_aj's answer above and threw it in a static class:
public static class TokenLifetimeValidator
{
public static bool Validate(
DateTime? notBefore,
DateTime? expires,
SecurityToken tokenToValidate,
TokenValidationParameters #param
) {
return (expires != null && expires > DateTime.UtcNow);
}
}
I just implemented a JWT token middleware too and although the examples on the internet use UtcNow, I had to use Now or the expire time is off. When I use Now, expiration is spot on.
Below link give you the exact answer, as by default MS have expire time of 5mins.
So either you have to make it custom or time which you will give in
expires: DateTime.Now.AddSeconds(30)
30seconds in above line will be added in expirey time. So total expire time will be 5mins and 30secs
https://github.com/IdentityServer/IdentityServer3/issues/1251
Hope this will help.
I have set the token expiry to 1 minute, but I am not getting the 401 unauthorized error after 1 minute.
Startup.cs
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Token Generation method:
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
int expiryMins = 1
var token = new JwtSecurityToken(_configuration["Jwt:Issuer"],
_configuration["Jwt:Issuer"],
null,
expires: DateTime.UtcNow.AddMinutes(expiryMins),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
As we discussed in the comments, this was due to the clock skew setting defaulting to 5 minutes, allowing tokens to be considered valid max 5 minutes after expiry (from the server's point of view that validates the token).
Clock skew setting exists because the server that issues the token and the server that validates the token might have slight differences in their clocks.
Usually it is a good idea to have some flexibility here and the default of 5 minutes is okay.
If you do not wish to have this behaviour, you can set ClockSkew on the TokenValidationParameters to 0 seconds.
Currently I am programming a ASP.NET-Core WebApi using JWT-Bearer-Authentication.
To make the API accessible from different timezones I am using the following Pattern to set the fields nbf (notBefore) and exp (expires) inside my JWT to a UTC-Timestamp:
var utcNow = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified);
...
var tokenOptions = new JwtSecurityToken(
notBefore: utcNow,
expires: utcNow.AddSeconds(3600),
);
...
For token generation, everything works pretty good, nbf and exp contain a UNIX-Timestamp representing the current UTC-Time.
But when doing token validation, everything works for 5 Minutes (my clock-skew setting) and then I only get 401 from API, because the token-validation is done with my current timezone here in Germany.
Is there a way to setup the JwtAuthentication-Middleware in .NET-Core to use UTC-Time for token-validation? Or are there any other ways to solve this?
For a more complete answer, in your Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// ...
ValidateLifetime = true,
LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken,
TokenValidationParameters validationParameters) =>
{
return notBefore <= DateTime.UtcNow &&
expires > DateTime.UtcNow;
}
};
});
It's already the case. The System.IdentityModel.Tokens.Jwt package does indeed validate JWT lifetime against UTC time. Here's the relevant bit from the source:
public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
...
if (notBefore.HasValue && expires.HasValue && (notBefore.Value > expires.Value))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10224, notBefore.Value, expires.Value))
{ NotBefore = notBefore, Expires = expires });
DateTime utcNow = DateTime.UtcNow;
if (notBefore.HasValue && (notBefore.Value > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)))
throw LogHelper.LogExceptionMessage(new SecurityTokenNotYetValidException(LogHelper.FormatInvariant(LogMessages.IDX10222, notBefore.Value, utcNow))
{ NotBefore = notBefore.Value });
if (expires.HasValue && (expires.Value < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())))
throw LogHelper.LogExceptionMessage(new SecurityTokenExpiredException(LogHelper.FormatInvariant(LogMessages.IDX10223, expires.Value, utcNow))
{ Expires = expires.Value });
// if it reaches here, that means lifetime of the token is valid
LogHelper.LogInformation(LogMessages.IDX10239);
}
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/b5b7ed8fb8ce513469b51b87c5f76314783b74e3/src/Microsoft.IdentityModel.Tokens/Validators.cs#L268
One solution is to validate the token without expiration time. This will return valid token even if the token had expired. Then in your code manually check the tokens expiration time. Here are snippets of the codes:
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = false, // we can check manually
ValidateIssuer = true,
ValidateAudience = true,
.
.
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
Then when token is validated, check the expiration time with:
public bool IsExpired(DateTime now)
{
return JwtSecurityToken.ValidTo < Date.UtcNow;
}
I hope this answer will help someone.
I am using
var payload = new JwtPayload(issuer, audience, claim identities, DateTime.Now, DateTime.Now.AddMinutes(60), DateTime.Now);
to generate auth token having 60-minute expiring time but it is expiring before the expiring time
I use the following method to build a token in my asp.net core web api service:
private string BuildToken()
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.Key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_jwtOptions.Issuer, // some issuer, e.x. you can specify your localhost
_jwtOptions.Issuer,
expires: DateTime.Now.AddMinutes(_jwtOptions.Expires), // int value
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Perhaps this helps you somehow.
I am looking for proper way how to implement authentification and authorization.
If I understand it well - this should be realized through "Identity" - it's offering both of these things I need.
My problem is that i can't use a database. I have to use a service (WCF service where our internal DDLs are connected to our system) which is able only Login (I give it user name and password) and after login i can get list of permissons.
I already saw articles how to have custom UserStore, RoleStore, UserManager and SignInManager.. but I am still confused and I don't know how to do it.
Is this even posible through Identity model? If not how I should do it please?
Thank you for every advice.
There are some articles which I already checked:
Microsoft - custom storage providers
Core indentity without entity framework
Sikorsky blog - custom user manager
In fact the WCF service is your authentication service. No need to implement this twice. A call to the service verifies the credentials and returns everything that should be in the access token.
Basically all you have to do is generate an access token using the information from the WCF service. And configure your app to use the created access token.
The flow can be like this: first make a call to the WCF service in order to verify the login and retrieve the information.
public async Task<IActionResult> LoginAsync([FromBody]UserLogin login)
{
var loginInfo = _wcf.LoginUser(login);
if (loginInfo == null)
return Unauthorized();
return Ok(CreateAccessToken(loginInfo));
}
To create an access token:
public class TokenHelper
{
public const string Issuer = "http://www.mywebsite.com/myapp";
public const string Audience = "http://www.mywebsite.com/myapp";
// This should not be hardcoded!
public const string Secret = "My_super_secret";
public AccessToken CreateAccessToken(LoginInfo loginInfo)
{
// Set expiration time of 5 minutes.
DateTime expires = DateTime.UtcNow.AddMinutes(5);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, loginInfo.UserId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
// Add custom claims, rolepermissions
if (loginInfo.Permissions != null && loginInfo.Permissions.Any())
loginInfo.Permissions.foreach(p => claims.Add(new Claim("Permission", p)));
if (loginInfo.IsUser)
claims.Add(new Claim(ClaimTypes.Role, "User"));
if (loginInfo.IsAdmin)
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
var token = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
expires: expires,
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Secret)),
SecurityAlgorithms.HmacSha256
)
);
return new AccessToken
{
ServerTime = DateTime.UtcNow.ToString("yyyyMMddTHH:mm:ssZ"),
Expires = expires.ToString("yyyyMMddTHH:mm:ssZ"),
Bearer = new JwtSecurityTokenHandler().WriteToken(token)
};
}
}
Where AccessToken is:
public class AccessToken
{
public string ServerTime { get; set; }
public string Expires { get; set; }
public string Bearer { get; set; }
}
And add authentication in your startup:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidIssuer = TokenHelper.Issuer,
ValidAudience = TokenHelper.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(
TokenHelper.Secret))
};
}
);
-- update --
_issuer, _audience and _secret are from some 'external source'. Meaning that all three are fixed string values, but the source (where the value is set) is variable.
For _issuer you usually use the url of the server that issues the token. Like http://www.mywebsite.com/myapp
The _audience is the application that is meant to accept the token. In this case _issuer and _audience are the same, so you can use the same value.
_secret is, well secret and can be any string, like 'my_super_secret'. This is something you want to stay secret. So you don't hardcode it, but get it from a safe location instead.
I've updated above code in a way so you can test it. Please note that secret should not be hardcoded.