JWT Token Validation in C#

(and not doing it wrong)

A blog post directly tied to something I'm doing at work - Like researching something FOR WORK!!! Not just related to, or ancillary to; but actual research for actual work. Crazy, I know.

The basic premise is that we're doing OAuth for our mobile app; which the server endpoints need to validate the token. The server is not the authenticating server. We'll need to share a cert and validate the token.

Starting Out

The original idea was to write our own token validation library. We started down this route. Got one whole unit test written... Then I found a library. AzureAd/azure-activedirectory-identitymodel-extensions-for-dotnet. Specially the System.IdentityModel.Tokens.Jwt NuGet package.
Another team, with similar needs, is investigating a few other libraries; but those libraries are heavy. they do a lot more. My requirements are to crack the token; validate the values; and return data or 401.
I don't need 90% of what is bundled with the library they are leaning towards.
What I really like about this library is that it's very modular; with each module being very focused. The JWT code is its own NuGet package. The SAML is its own NuGet package. This minimizes the confusion while working on setting up validation.

One downside to this library; there's not a lot of documentation on how to use it. I think it'll be pretty obvious once I understand it.

During a lecture a professor formulated a theorem and said: "The proof is obvious". After a student asked, "Is it obvious?", the professor thought for a minute, left the lecture room, returned after 15 minutes and happily concluded: "Indeed, it is obvious!"

Digging In

I've cloned the source for the repo; unloaded all but the JWT project (and dependencies). This should make it easier if/when I try to search for things. I did some searching early on when I was looking into this library... some terms return a lot of results.

There is a Validators class which has a few validate* methods. The API to these is a little strange; one of the params isn't used. It's extensive checking, but I'm not sure it's intended purpose is to ... oddly, actually validate. The methods will validate... As an example of my uncertainty; the ValidateIssuer method docs say, "The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".". It will return the value provided if it's valid. I don't need the string back; I have it... I need to know if it's valid or not.
It will throw an exception if it's not valid (or not found) - but the return of the issuer string just seems... non-intuitive. Along with the rest of the Validate* methods being void. It stands out.

The Token that will be consumed has additional information which will need custom validation. I haven't seen anything in the project to allow validation of custom elements in a JWT. This will hen require custom validators. I'm wondering if I should just do a custom implementation of the four validations offered.

Oooo.... The JwtSecurityTokenHandler has a ValidateToken method. This looks kinda promising. Yep; this calls the four methods I was looking at earlier. Also looks to have decryption methods. Excellent; I'll start poking at this class. Unfortunately, I don't have an encrypted token yet. Yes - I can easily encrypt it and test; I will - Just rolling with simple first. I have a token; now to validate it.

Token Validation

The identity provider has used returns multiple tokens; access, id, and refresh. The library decryption might be usable, but I can't see anywhere in the library to parse this top level structure. This will need to be deserialized before being able to validate the tokens. Except for the refresh; that's not a JWT token. It's a mystery token of black-box magic. Or... something I don't care about.

days later

I've done a bit of pounding my head against this, but finally am able to validate the token.

The initial problem was that the ValidateToken method requires the signature to successfully validate. I had to get the ... I'm assuming public key for the X509Certificate... but..., not 100% it's the public.; so I'm not sharing it. I probably wouldn't have shared it anyway.

Once I had this; I could build up Signing Credentials that are used by the validation

        private const string X509Cert = "THE_VALUE_YOU_DON'T_GET_TO_SEE";
        public static X509Certificate2 DefaultCert_Public_2048 = new X509Certificate2(Convert.FromBase64String(X509Cert));
        public static X509SecurityKey DefaultX509Key_Public_2048 = new X509SecurityKey(DefaultCert_Public_2048);
        public static SigningCredentials DefaultX509SigningCreds_Public_2048_RsaSha2_Sha2 = new SigningCredentials(DefaultX509Key_Public_2048, SecurityAlgorithms.RsaSha256Signature);

A large chunk of that was copied directly from the identity model unit tests. Maybe all of it.

Once you have the JWT token to validate; and configured the SigningCredentials you're ready to Validate!!!

[TestMethod]
public void TokenValidation()
{
    SecurityToken validatedToken;
    TokenValidationParameters validationParameters = new TokenValidationParameters();
    validationParameters.IssuerSigningKey = DefaultX509Key_Public_2048;

    new JwtSecurityTokenHandler().ValidateToken("BASE64_ENCODED_JWT_TOKEN_GOES_HERE", validationParameters, out validatedToken);
    }

We run the test AND!!!! ... failure. OK; I knew that'd happen, but I'm doing this in steps.

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10208: Unable to validate audience. validationParameters.ValidAudience is null or whitespace and validationParameters.ValidAudiences is null.

We need to provide the Audience of the token.

private const string ValidAudience = "YOUR_AUDIENCE_VALUE_HERE";

[TestMethod]
public void TokenValidation()
{
    SecurityToken validatedToken;
    TokenValidationParameters validationParameters = new TokenValidationParameters();
    validationParameters.IssuerSigningKey = DefaultX509Key_Public_2048;
    validationParameters.ValidAudience = ValidAudience

    new JwtSecurityTokenHandler().ValidateToken("BASE64_ENCODED_JWT_TOKEN_GOES_HERE", validationParameters, out validatedToken);
}

Awesome; that's set up; let's run the test!
It did break again; I wasn't 100% it would, but glad it did.

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException: IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null.

We also need to validate the issuer.

private const string ValidAudience = "YOUR_AUDIENCE_VALUE_HERE";
private const string ValidIssuer= "YOUR_ISSUER_VALUE_HERE";

[TestMethod]
public void TokenValidation()
{
    SecurityToken validatedToken;
    TokenValidationParameters validationParameters = new TokenValidationParameters();
    validationParameters.IssuerSigningKey = DefaultX509Key_Public_2048;
    validationParameters.ValidAudience = ValidAudience
    validationParameters.ValidIssuer = ValidIssuer;

    ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken("BASE64_ENCODED_JWT_TOKEN_GOES_HERE", validationParameters, out validatedToken);
}

OK - Running again... This time I expect it to pass.
...crosses fingers

YAY! Passed.

This is also doing a date check. If the token was expired the test would throw the following exception

Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '03/27/2017 15:32:51'
Current time: '03/27/2017 16:13:35'.

And now you know when I wrote the post. :-P

There are 4 values of the token being validated; Lifetime, Signing, Audience, Issuer. I don't know if there are additional values that /must/ be checked as a matter of good practice. I suspect there are.
One of the things I need to do for work gets and pass along a piece of Claim information. It's a user identifier that we need for downstream. Blah blah blah... Need to validate a CLAIM!

The JwtSecurityTokenHandler#ValidateToken returns a ClaimsPrincipal which we can use. It has the SecurityToken out param... I'm not sure what that does right now. It's a JwtSecurityToken as it's actual class. If we wanted to cast, we could access some additional JWT info; but... I'm not going to be concerned with that.
We'll go with the returned ClaimsPrincipal. It has Claims on it, and we pull the data off of this.

Claim claim = principal.Claims.First("claimKey")

There's a shorthand for this on the ClaimsPrincipal

Claim claim = principal.FindFirst("claimKey");

As a test for the custom, or additional, values it can be in a simple form like

private const string ValidAudience = "YOUR_AUDIENCE_VALUE_HERE";
private const string ValidIssuer= "YOUR_ISSUER_VALUE_HERE";

[TestMethod]
public void TokenValidation()
{
    SecurityToken validatedToken;
    TokenValidationParameters validationParameters = new TokenValidationParameters();
    validationParameters.IssuerSigningKey = DefaultX509Key_Public_2048;
    validationParameters.ValidAudience = ValidAudience
    validationParameters.ValidIssuer = ValidIssuer;

    ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken("BASE64_ENCODED_JWT_TOKEN_GOES_HERE", validationParameters, out validatedToken);

    Assert.AreEqual("expectedClaimKeyValue", principal.FindFirst("claimKey").Value);
}

That is the basic of what I'm looking to build upon for a custom JWT token validator lib.

The additional expected fields will be encapsulated in a containing class. I don't know all the details for that, but this work has just been a spike to get enough information to get started!