Validating an ADFS JWT token

Rory Braybrook
The new control plane
3 min readJan 15, 2018

I’ve been looking at integrating ADFS on Server 2016 (aka ADFS 4.0) with different kinds of applications and the question always comes up regarding how a 3rd party API validates the access token it receives.

With ADFS, the access token isn’t simply a GUID. It’s a proper JWT token with “aud”, “iss” etc.

Auth0 has a very good site devoted to JWT tokens. It will decode the token for you plus it has a whole collection of packages plus details of the functionality of each package and what the package validation checks e.g. for .NET:

Note: The steps to validate a JWT are described in RFC 7519.

Based on the library and with some help, I wrote a small sample to demonstrate how to do the validation.

You need to update “const string testToken” in the sample with your JWT.

The OpenID Connect “metadata” for ADFS is here:

https://my-adfs/adfs/.well-known/openid-configuration

Inside this, there is a link to the key file:

https://my-adfs/adfs/discovery/keys

which contains:

{
“keys”: [{
“kty”: “RSA”,
“use”: “sig”,
“alg”: “RS256”,
“kid”: “0StYWibgHy3QtM6a6wGHsOZHOvM”,
“x5t”: “0StYWibgHy3QtM6a6wGHsOZHOvM”,
“n”: “njQ…36Q”,
“e”: “AQAB”,
“x5c”: [“MII…T6w==”]
}]
}

I derived the token from this sample.

The token looks like:

Note that the “x5t” value in the decoded header matches the value in the metadata.

One of the things the IdentityModel.tokens.jwt class needs to check is the signature. This uses the certificate in the “x5c” field in the key metadata. (If you want to check the signature in the decoded token, do this).

The class also checks the issuer in the token (in this case ADFS). It checks that the audience is actually your application. It also checks that the token has not expired etc.

Once the class has validated the token, it is displayed by the sample:

Token is validated

Claim is aud: https://localhost:44326/
Claim is iss:
http://my-adfs/adfs/services/trust
Claim is iat: 1515978453
Claim is exp: 1515982053
Claim is apptype: Public
Claim is appid: 77403d77-fcd0–49c6–9bb7-c114ae05e349
Claim is
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod: urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
Claim is auth_time: “2018–01–15T01:07:33.366Z”
Claim is ver: 1.0

You may hit some exceptions. In a lot of the cases, the exception shows the value it is expecting and the value it actually received. A useful tip is to look at the values in the decoded token and then compare with the values configured in the sample.

IDX10503: Signature validation failed. Keys tried: ‘Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: 0StYWibgHy3QtM6a6wGHsOZHOvM

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException
HResult=0x80131500
Message=IDX10205: Issuer validation failed. Issuer: ‘
http://my-adfs/adfs/services/trust'. Did not match: validationParameters.ValidIssuer: ‘https://my-adfs/adfs/' or validationParameters.ValidIssuers: ‘null’.

Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException
HResult=0x80131500
Message=IDX10223: Lifetime validation failed. The token is expired.
ValidTo: ‘01/14/2018 22:21:05’
Current time: ‘01/14/2018 22:29:54’.

“IDX10709: JWT is not well formed: ‘ey…nqw’. The token needs to be in JWS or JWE Compact Serialization Format. (JWS).

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5