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.
Related
I have an end point which is protected by the [Authorize] attribute. I want to allow access to it, when a token is provided that is signed with the phrase "Super-Secret-Key" For now, I just generate the token with Jwt.io (you can see a screenshot at the end of the post). However, when I visit the end point, just get a 401 Unauthorized, even if I send the encoded token along as a bearer token (using Postman).
What am I missing?
Program.cs
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
var builder = WebApplication.CreateBuilder(args);
{
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => options.TokenValidationParameters = tokenValidationParameters);
builder.Services.AddAuthorization();
builder.Services.AddControllers();
}
(...)
the issue is that asp.net core tries to validate the token issuer by default that you did not set up on jwt.io. Set ValidateIssuer = false inside TokenValidationParameters:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
});
If you want to specify an issuer to your JWT token you have to add the iss standard claim (Payload data) as specified inside official docs: https://jwt.io/introduction.
Here's an example:
Validation setup:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateIssuer = true, //Validate issuer
ValidIssuer = "MyIssuer", //Issuer to validate
//ValidIssuers = new List<string>() { "MyIssuer", "MySecondIssuer" }, //You can specify multiple issuers
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
});
There also are other "standard" (registered) claims that you can use like:
exp: timestamp that identifies the expiration time of the token.
nbf: timestamp that identifies the time before which the JWT
must not be accepted for processing.
aud: audience identifies recipients that the JWT is
intended for.
that you can validate with standard validation options or with custom-written validation logic.
See: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
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 getting following exception.
I have use post method to get token using service principal
Message = "IDX10708: 'System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler' cannot read this string
I am generating access token using ""https://login.microsoftonline.com/{TenantId}/oauth2/token" and trying to validate that
SecurityToken securityToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
IConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
$"https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true
};
var userClaims = handler.ValidateToken(token, validationParameters, out securityToken);
return userClaims.Claims.Where(claim => claim.Type == "appid").FirstOrDefault().Value;
I am getting exception while validating
Please try to validate the code by putting try/catch block and review the claims value in the debugger e.g. uniquer_name etc
you can follow this thread for more clarity:-
When is JWTSecurityTokenHandler.ValidateToken() actually valid?
Hope it helps.
I have an asp net core api where I need a custom validation for the "audience" claim in a JWT token. This is possible via the AudienceValidator delegate (Microsoft.IdentityModel.Tokens) with signature:
bool CustumValidator(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
The audiences should, as I understand it, contain a list of audiences in the token. The validationParameters contains all the TokenValidationParameters you register at startup. However, for me this parameter is empty.
In the configure method of my startup class if have the following:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime)
{
//...
// Setup JWT for the application.
var jwtAppSettingOptions = ConfigurationRoot.GetSection(nameof(JwtIssuerOptions));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
//ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)], -- I have tried with both this property and the ValidAudiences one below
ValidAudiences = new[] { jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)] },
ValidateIssuerSigningKey = true,
IssuerSigningKey = _jwtSigningKey,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
AudienceValidator = DoValidation // <-- my custom validation method, described below
};
//....
}
And then I have this little test method, that for now, does nothing but return true.
private bool DoValidation(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
var castedToken = securityToken as JwtSecurityToken;
// Do Nothing
return true;
}
As said, this token in the piece above has no value in its audience property, and the IEnumerable<string> audiences is empty as well. I do not understand why. If I let the default validator method be (by not setting a delegate), it is able to see the token's audience and Forbids access if the audience is incorrect. So why does my custom method not get the audience(s) passed in?
Here's how I make the token:
var jwt = new JwtSecurityToken(
_jwtIssuerOptions.Issuer,
String.IsNullOrWhiteSpace(_aud) ? _jwtIssuerOptions.Audience : _aud,
claims,
_jwtIssuerOptions.NotBefore,
_jwtIssuerOptions.Expiration,
_jwtIssuerOptions.SigningCredentials);
Thanks in advance.
I came across this question while trying to find the signature for the AudienceValidator delegate. (Thanks for that!)
The audiences enumerable is populated for me when I handle that delegate. One difference I see is that I do not specify the ValidAudiences parameter. You might try excluding that (assuming you haven't already solved this). Here is an example of my working code:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "MyIssuer",
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["SecurityKey"])),
AudienceValidator = validateAudience
};
});