How to validate ID Token via IdentityModel

ID tokens are used in OpenID Connect to login users into applications. With regard to the OpenID Connect specification the application must validate the ID token in order to ensure the token’s integrity and authenticity.

There are basically two steps to perform validation:
– validate the ID token signature
– validate the ID token claims

Signature validation
When the ID token is protected with digital signature we need to find out what the crypto algorithm has been selected for the application at registration time. In most cases it should be RS256 algorithm. It is default algorithm supported by all OpenID providers. Then we need the JSON Web Key. It can be downloaded from the identity provider configuration through JWK set endpoint. The structure of JWK is similar to the following one:

{
   "kty": "RSA",
   "alg": "RS256",
   "use": "sig",
   "kid": "6c7911b8af40ca6e9b83ec2e5614c7e6d7825a0f",
   "n": "xc8X5kFAfKoP9Ptqc0zIAhuAlVUqHso5FSHjOaHzzgZzgGKbJO8Iywun18tAAPRYavJGrWioIM8mMT7BJmdsp2FE5L8tsCWdvOg87OqZzZ2w1yiiBXz1xJpVca2OZQ71BxBgZo4IEWUOTemuFwH1eENea8SsQadpHxfyl0ds4YmcDUbLAY7tiaZdu8uDbRXuvi02zHTIjdtOPkNxzBGjzTObsziLtiuIfVO3niphsMEQuxggPANoR5woiCJeXAsTkdc8ivB9I8sFq0GZQMEL0bfY9VI8yei6HBl2GQRPWxEpwsRpNSr3dm6c4yYCnSEOWu6pcfczUIMPFwgMw",
   "e": "AQAB"
}

We look for public key. It is represented here by two properties n and e which stand for modules and exponent respectively. Having those values we can initialize RSACryptoServiceProvider like that:

var publicKeyModules = "xc8X5kFAfKoP9Ptqc0zIAhuAlVUqHso5FSHjOaHzzgZzgGKbJO8Iywun18tAAPRYavJGrWioIM8mMT7BJmdsp2FE5L8tsCWdvOg87OqZzZ2w1yiiBXz1xJpVca2OZQ71BxBgZo4IEWUOTemuFwH1eENea8SsQadpHxfyl0ds4YmcDUbLAY7tiaZdu8uDbRXuvi02zHTIjdtOPkNxzBGjzTObsziLtiuIfVO3niphsMEQuxggPANoR5woiCJeXAsTkdc8ivB9I8sFq0GZQMEL0bfY9VI8yei6HBl2GQRPWxEpwsRpNSr3dm6c4yYCnSEOWu6pcfczUIMPFwgMw";
var publicKeyExponent = "AQAB";

var rsa = new RSACryptoServiceProvider();

rsa.ImportParameters(new RSAParameters()
{
   Modulus = Convert.FromBase64String(publicKeyModules),
   Exponent = Convert.FromBase64String(publicKeyExponent)
});

The next part is ID token claims validation. The RP must ensure that retrieved token comes from trusted issuer or has not expired, for instance.

Claims validation
ID Token carries claims encoded in JSON similar to the following:

{
   "sub"       : "anna",
   "iss"       : "https://identityprovider.com",
   "aud"       : "relying-party-name",
   "nonce"     : "n-0S8_ZzA4Mj",
   "auth_time" : 1515280969,
   "iat"       : 1515280970,
   "exp"       : 1515281970,
}

At this stage we need to validate as much claims as necessary to ensure authenticity. The common scenario is to validate issuer, audience, expiration and nonce claim if it has been supplied. What claims are validated or not can be controlled through TokenValidationParameters instance:

var parameters = new TokenValidationParameters 
{ 
   RequireExpirationTime = true, 
   RequireSignedTokens = true, 
   ValidateAudience = true, 
   ValidateIssuer = true, 
   ValidateLifetime = true, 
   ValidIssuer = "https://identityprovider.com", 
   ValidAudience = "relying-party-name", 
   IssuerSigningKey = new RsaSecurityKey(rsa) 
};

Please note that here we are passing security key in order to validate token signature.

The actual validation process is triggered via ValidateToken method of JwtSecurityTokenHandler instance:

SecurityToken jwt; 
var handler = new JwtSecurityTokenHandler()
var principal = handler.ValidateToken(token, parameters, out jwt);

Because the nonce claim is not validated automatically we need to do it ourselves:

var nonceClaim = principal.FindFirst("nonce"); 

if (!string.Equals(nonceClaim.Value, nonce, StringComparison.Ordinal)) 
{ 
   throw new Exception("invalid nonce"); 
}

At this stage the ID token is validated and if all steps succeed so far then the application can sign user in.

Leave a Reply

Your email address will not be published. Required fields are marked *