Validating JWT Tokens explained

This blog post contains no information you can’t find in the open standards specifications or in other good blogs out there, but I do feel the need to write it anyway since I do get the question ever so often – how do you validate a JWT token? I will show you and write a very simple dotnet console program to do just that. The github repo for this project can be found here.

What is a JWT Token?

A JWT Token is defined in RFC7529 and is adequately explained in wikipedia. I consists of three sections – a header, a payload and a signature – concatenated together with a “.” where each part is a base64 encoded and the first two are in json. This should not be news to most people.

Self-signed JWT Token

You can generate a JWT token yourself without the use of an Identity Provider, like Azure AD B2C, but then it is a bit difficult for the verifier to validate the token unless it shares some secrets with the issuer. In the below screenshot, we have a self-signed JWT token that was signed with a symmetric key where we added the id, iss and the aud claim just because we can – not that we need to. The other fields, in both the header and the payload, are automatically generated

The dotnet code for generating this JWT token looks like the below. We are passing a key/value pair for the claim ‘id’ and we are also passing a byte array to be used as symmetric key for encryption. Symmetric means that it is not one-way and that we can use it for both encrypt AND decrypt.

private byte[] privateSigningKey = Encoding.ASCII.GetBytes("This-is-my-magic-private-secret-that-will-be-used-to-sign-a-private-JWT");
GenerateJwtToken( "id", Guid.NewGuid().ToString(), privateSigningKey );

public string GenerateJwtToken( string claimName, string claimValue, byte[] signingKey)
{
    var tokenHandler = new JwtSecurityTokenHandler();            
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[] { new Claim( claimName, claimValue ) }),
        Expires = DateTime.UtcNow.AddMinutes(30),
        Issuer = issuer,
        Audience = audience,
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

Validating a JWT Token – basics

When you validate a JWT token, you do the following

  1. Check that it is still valid and has not expired
  2. Check that it was issued by the issuer you expect (optional)
  3. Check that it was issued for the audience you expect (optional)
  4. Check that it was signed by the issuer in the way you expect

Expiery. The first test is simple. The issuer determines the lifetime of the JWT token, when it begins and when it ends. Anytime between and the token is ok to use and anytime outside means that the token has expired. The business rule can be expressed as: nbf <= now < exp.

Issuer and audience. The issuer and the audience has to do with setting the expectation that the conversation should be between the parties you expect it to be. If you are an application that trusts Identity Provider X, you expect the issuer of the JWT token to be X. If not, someone is trying to fool you. Also, if your app is called Y, and it trusts X, you expect the JWT token to be issued to audience Y, otherwise what is happening is that someone else who can get a JWT token from X is trying to give you their token. Is that bad? Well, imagine that your app holds the launch codes to your countries nuclear missiles and someone is trying to send you a token for the car wash API. What could go wrong?

Signing. Disregarding the above validations, how can you really know that X issued the token? One way would be to delegate that responsibility to the issuer, but that introduces two new problems: 1) you would need to call the issuer, and that would be an enormous overhead if every app in the world would need to phone home every time it needed to validate a JWT token. 2) The issuer would still need some kind of signature so it could do the work (ie, it would need to solve the problem – did I really issue this?).

So, how does it work? Well, there are only two ways to make the JWT token validation stateless, and that is 1) you possess the symmetric key the JWT token was signed with, or 2) you can get the public keys matching the private key the issuer used to sign it with, so you can do the validation yourself.

Validating a JWT Token using a symmetric key

If the JWT token is self signed and you happen to have the symmetric key is was signed with, you can validate it in dotnet using the following code:

var tokenHandler = new JwtSecurityTokenHandler();
try {
    tokenHandler.ValidateToken(token, new TokenValidationParameters {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(signingKey),
        ValidateIssuer = true, 
        ValidIssuer = iss,
        ValidateAudience = true, 
        ValidAudience = aud,
        ClockSkew = TimeSpan.Zero // tokens expires on the exact second
    }, out SecurityToken validatedToken);
    var jwtToken = (JwtSecurityToken)validatedToken;
    Console.WriteLine("*** Valid token ***\n{0}", jwtToken);
    return true;
} catch( Exception ex ) {
    Console.WriteLine(ex.Message);
    return false;
}

You see in the code that you can control how you want to validate the JWT token, ie if you want to validate the issuer, audience and even the signing of the token. So if you generate a JWT token that you sign with your own symmetric key, you will have no problem validating it if the validation code also has access to the same key.

Validating a JWT Token issued by an Identity Provider

The above poses a problem, because if you are using any type of decent Identity Provider, like Azure AD, you just don’t have access to to the private keys used for signing and you certainly don’t would start pass those keys around to every app out there that needed to validate a token. You need a model where the validation can happen in the app in a stateless fashion inside the webapp/API. The solution is the .well-known/openid-configuration data which points to an endpoint where you can retrieve the JWKS public keys.

In the .well-known/openid-configuration endpoint for most decent Identity Providers, it has an endpoint link called jwks_uri that points to where we can find the Identity Providers public keys. Note also that the metadata gives information on what the expected value of the iss claims shoud be. This helps us with validation problem #2 above.

What you get whet you retrieve the jwks_uri metadata is a collection of keys, where each key is identified by a kid (key id) attribute. Each JWT token issued by the Identity Provider will have the kid marked in its JWT header so you know which key was used to signed it. Then, notice the e and the n attributes which will pay an important role below.

Putting all together in code

So if you need to validate a JWT token issued by Azure AD B2C, what would you have to do?

First, you need to know the .well-known/openid-configuration endpoint to your B2C instance so you can download the metadata. You usually configure this directly or indirectly on webapps or webAPIs (indirectly means you configure it in portions and your library formats the url for your).

The .welll-known/openid-configuration to my dogfood Azure AD B2C tenant is this (it will open in a separate tab). So in code, I would need to do a HTTP GET to that, parse the response and do another HTTP GET to the jwks_uri to get the public keys my B2C instance is currently using. This is a one time operation – or an operation you need to do very seldom (if your app is long running, you probably should reload this every 6-12-24 hours or so since B2C may rotate the keys).

Validating the issuer, audience and expiry is no different from a self-signed token, but how do you validate the signing with the public key? Well, in dotnet, you need to create a key class called JsonWebKey and the code looks like the below. The kid you pass as method parameter is from the JWT header and what you do is to look for a kid in the metadata you retrieved from the jwks_uri endpoint. When we create a JsonWebKey class instance, we pass the kty (key type = sig, for signing) and then also the e and the n values which are the public key. So if we just find the matching kid, we have enough info from jwks_uri to create the JsonWebKey.

public JsonWebKey CreateJsonWebKey( string kid )
{
    JsonWebKey jwk = null;
    // find matching 'kid' in metadata from jwks_uri
    foreach (var key in this.b2cJwksConfig["keys"]) {
        if (key["kid"].ToString() == kid ) {
            jwk = new JsonWebKey() {
                Kid = key["kid"].ToString(),
                Kty = key["kty"].ToString(),
                E = key["e"].ToString(),
                N = key["n"].ToString()
            };
        }
    }
    return jwk;
}

So the validation code for a JWT token would then be as below (there are a lot out code doing Console.WriteLine that is there for demo purposes).

if ( LoadB2CMetadata( wellKnownOidcConfigurationEndpoint ) ) {
     ValidateJwtToken(b2cToken, b2cIssuer, b2cAudience, null);
}

public bool ValidateJwtToken(string token, string iss, string aud, byte[] signingKey)
{
    var jwtHeader = GetJwtHeader(token);
    Console.WriteLine("*** JWT Token header ***\n{0}", jwtHeader.ToString());

    // just get the JWT claims so we can output them to the console
    var jwtPayload = GetJwtPayload(token);
    int nbf = int.Parse(jwtPayload["nbf"].ToString());
    int exp = int.Parse(jwtPayload["exp"].ToString());
    string jwtIss = jwtPayload["iss"].ToString();
    string jwtAud = jwtPayload["aud"].ToString();

    // just so we can show if the token is expired in a sensible way
    int secondsSinceEpoch = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
    string time = string.Format("{0} <= {1} < {2}", nbf, secondsSinceEpoch, exp);

    SecurityKey signKey = null;
    string signingKeyMsg = "";
    // self-signed or signed by an OIDC provider
    if (signingKey != null ) {
        signKey = new SymmetricSecurityKey(signingKey);
        signingKeyMsg = "<SymmetricSecurityKey>";
    } else {
        Console.WriteLine("*** creating a JsonWebKey() ***");
        signKey = CreateJsonWebKey(jwtHeader["kid"].ToString());
        signingKeyMsg = jwtHeader["kid"].ToString();
    }
    Console.WriteLine("*** Validating token ***\n- issuer:\t{0} == {1}\n- audience:\t{2} == {3}\n- not expired:\t{4}\n- signing key:\t{5}"
        , iss, jwtIss, aud, jwtAud, time, signingKeyMsg );

    var tokenHandler = new JwtSecurityTokenHandler();
    try {
        tokenHandler.ValidateToken(token, new TokenValidationParameters {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signKey,
            ValidateIssuer = true, 
            ValidIssuer = iss,
            ValidateAudience = true, 
            ValidAudience = aud,
            ClockSkew = TimeSpan.Zero // tokens expires on the exact second
        }, out SecurityToken validatedToken);
        var jwtToken = (JwtSecurityToken)validatedToken;
        Console.WriteLine("*** Valid token ***\n{0}", jwtToken);
        return true;
    } catch( Exception ex ) {
        Console.WriteLine(ex.Message);
        return false;
    }
}
private bool LoadB2CMetadata( string wellKnownOidcConfigurationEndpoint )
{
    Console.WriteLine("*** Getting OIDC metadata ***\n{0}", wellKnownOidcConfigurationEndpoint );
    if (!HttpGet( wellKnownOidcConfigurationEndpoint, out HttpStatusCode statusCode, out string oidcConfig)) { 
        return false;
    }
    var json = JObject.Parse(oidcConfig);
    string jwks_uri = json["jwks_uri"].ToString();
    b2cIssuer = json["issuer"].ToString();

    Console.WriteLine("*** Getting JWKS_URI ***\n{0}", jwks_uri);
    if (!HttpGet(jwks_uri, out statusCode, out string jwksConfig)) {
        return false;
    }
    Console.WriteLine("*** IDP Public Keys ***");
    this.b2cJwksConfig = JObject.Parse(jwksConfig);
    foreach( var key in b2cJwksConfig["keys"] ) {
        Console.WriteLine("kid:\t{0}\nkty:\t{1}", key["kid"], key["kty"] );
    }
    return true;
}

After you have validate your JWT token, there may be more work in your app/API where you need to check other claims, like scopes and roles, so you can distinguish between a token with scope User.Read.All and USer.ReadWrite.All.

Do I need to write code like this in my app?

No, you don’t, since most languages, like dotnet, have functionality like this built in. If you look at this dotnet API, all you need to do is:

  1. Add information about your Identity Provider to appsettings.json
  2. In ConfigureServices, in Startup.cs, add a call to services.AddAuthentication uses that appsettings.json
  3. In your Controllers, add [Authorize] on either the entire class or on specific methods that should require a valid JWT token.