-
Notifications
You must be signed in to change notification settings - Fork 414
ValidatingTokens
Token Validation has multiple parts. The token is validated by checking that it is for the application, that it was issued by a trusted Identity Provider (IDP), that the token's lifetime is in range, and that it was not tampered with. There can also be special validations. For instance, it is possible to validate that signing keys (when embedded in a token) are trusted and that the token is not being replayed. Finally, some protocols require specific validations.
Normally we obtain SecurityKeys for you by reaching out to the IDP (Discovery or Metadata). Sometimes it is necessary to obtain keys dynamically at runtime. Perhaps they are in a database, cached somewhere or Mutual TLS is being used. Here is how to do that. Dynamically obtaining signing keys at runtime
The validation steps are captured into Validators, which are all in one source file: Microsoft.IdentityModel.Tokens/Validators.cs
The validators are the following:
Validator | Description |
---|---|
ValidateAudience |
Ensures that the token is indeed for the application that validates the token (for me) |
ValidateIssuer |
Ensures that the token was issued by a STS I trust (from someone I TRUST) |
ValidateIssuerSigningKey |
Ensures the application validating the token trusts the key that was used to sign the token (this is a special case where the key is embedded in the token, usually this is not required) |
ValidateLifetime |
Ensures that the token is still (or already) valid. This is done by checking that the lifetime of the token (notbefore, expires) is in range |
ValidateTokenReplay |
Ensure the token is not replayed (this is a special case for some onetime use protocols) |
In addition to these validators, there are protocol specific validation rules. For example, OpenIdConnect requires the audience (‘aud’) claim to exist. See:Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs#L382
Pattern for using the JsonWebTokenHandler
var jsonWebTokenHandler = new JsonWebTokenHandler();
var tokenValidationResult = jsonWebTokenHandler.ValidateToken(token, validationParameters);
if (!tokenValidationResult.Isvalid)
{
// Handle each exception which tokenValidationResult can contain as appropriate for your service
// Your service might need to respond with a http response instead of an exception.
throw tokenValidationResult.Exception;
}
The TokenValidationResult.Exception property contains the actual exception. Your code should ensure the following tests are performed by either unit tests or integration tests which runs part of your build and release pipeline.
You must demonstrate that your code will fail with an Access Denied for the following exceptions, not for JsonWebTokenHandler that would be the exception property of the TokenValidationResult.
The simplest test to perform is:
* SecurityTokenInvalidSignatureException (Signature validation failed)
Additionally your tests should also cover these tests below:
* SecurityTokenExpiredException (Token expired)
* SecurityTokenInvalidAudienceException (Audience validation failed)
* SecurityTokenInvalidIssuerException (Issuer validation failed)
* SecurityTokenSignatureKeyNotFoundException (There is no key in metadata available to validate the token)
There are many more checks to do but these are the minimum to ensure you have negative testing for.
Note: SecurityTokenValidationException is the base exception
The TokenValidationParameters
class gives you the option of providing validation delegates for a variety of properties on the token. By implementing the validation delegate and setting it on the TokenValidationParameters, you can add in custom validation logic (e.g. custom lifetime validation).
- If writing custom validators you are responsible for ensuring the code fails correctly!
- You should always ensure that you do proper negative testing. Failing to properly validate a token means you will potentially allow people into your service which are not intended to have access.
The validation delegates present on TokenValidationParameters
are:
public delegate SecurityToken SignatureValidator(string token, TokenValidationParameters validationParameters);
public delegate bool TokenReplayValidator(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters);
public delegate string TypeValidator(string type, SecurityToken securityToken, TokenValidationParameters validationParameters);
public delegate bool AlgorithmValidator(string algorithm, SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters);
public delegate bool AudienceValidator(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters);
public delegate string IssuerValidator(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters);
public delegate bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters);
Example of a custom lifetime validation delegate:
public static bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters validationParameters)
{
if (...)
return true;
else (...)
return false;
}
Setting the validation delegate:
var tokenValidationParameters = new TokenValidationParameters();
tokenValidationParameters.LifetimeValidator = CustomLifetimeValidator;
If a validation delegate is provided, it is responsible for ALL validation involving the property in question. The library will NOT do any additional checks. This also means throwing an exception if the validation should fail.
Conceptual Documentation
- Using TokenValidationParameters.ValidateIssuerSigningKey
- Scenarios
- Validating tokens
- Outbound policy claim type mapping
- How ASP.NET Core uses Microsoft.IdentityModel extensions for .NET
- Using a custom CryptoProvider
- SignedHttpRequest aka PoP (Proof-of-Possession)
- Creating and Validating JWEs (Json Web Encryptions)
- Caching in Microsoft.IdentityModel
- Resiliency on metadata refresh
- Use KeyVault extensions
- Signing key roll over