Creating a JWT Validation Web App

This post has been republished via RSS; it originally appeared at: Azure Developer Community Blog articles.

JWT.png

 

If you have been doing any OAuth and/or Open ID Connect troubleshooting in recent years it's very likely you've run across https://jwt.io, or in the Microsoft world https://jwt.ms. Great tools, but primarily JWT parsing tools rather than JWT validation tools.

 

Yes, jwt.io allows you to upload your keys in the UI, but there are a lot of scenarios that doesn't work for.

 

Let's rewind a step or two here first though. Last year I showed how you could create your own faux tokens. That is; the tokens were real enough, but they mimicked actual tokens as they would look if issued by Azure AD and Azure AD B2C without actually being signed by Microsoft's keys. If you pasted the result into jwt.ms it would look like a real token.

 

Identity is a large topic, but the two central concepts are "who are you" (authentication) and "what are you allowed to do" (authorization). If your approach in web app or api is to accept any old token and say "this looks good" you might run into trouble.

 

Which is why I wanted to show how to  build a web app that will attempt to validate the token in addition to parsing it. This is based on supplying a metadata url to retrieve signing keys, etc. If you don't have this the app will just parse the token and check against the attributes you supply (issuer, audience).

 

The code for this post can be found here:

https://github.com/ahelland/Identity-CodeSamples-v2/tree/master/blazor_jwt_validator_dotnet_core 

 

In the real world i don't actually recommend you to do all these steps manually. There are libraries that will handle most of these things for you like MSAL on the client side of Identity.Web on the backend side. It is however always useful to dig a little deeper to gain an understanding of how it works behind the scenes.

 

There are third party libraries available for token validation that support a large range of algorithms and formats, but to keep it simple I'm just using components native to the .NET toolbox.

 

Step 1 - Is it a JWT that has been pasted in?

The web app will allow pretty much any text to be passed in so we need to validate that it is actually a token.

 

 

 

 

JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); //Check if readable token (string is in a JWT format) var readableToken = handler.CanReadToken(Jwt.Base64Token); if (readableToken != true) { FormatStatus = "The token doesn't seem to be in a proper JWT format."; return; } if (readableToken == true) { FormatStatus = "The token seems to be in a proper JWT format."; }

 

 

 

 

Step 2 - Do we have metadata?

To be able to leverage concepts like public/private key cryptography we need to exchange the keys used, and we also need to agree on a couple of other attributes the tokens contain. It is entirely possible to exchange this in a manual way whether that is sending files per email or reading them loud over the phone for that matter, but the recommended approach is to have the identity provider expose a metadata endpoint. Loading things in a more manual way also requires more code, so we've kept it simple here and only provide the option for using an endpoint. (If you don't have one the code will not break, but revert to just parsing the token.)

 

If you followed my previous post you should be able to provide your own endpoint even if that's just pointing to a different port on https://localhost.

https://techcommunity.microsoft.com/t5/azure-developer-community-blog/generating-azure-ad-quot-look-alike-quot-tokens/ba-p/1163098 

https://github.com/ahelland/Identity-CodeSamples-v2/tree/master/blazor-jwt_generator-dotnet-core 

 

 

 

 

//Load Metadata if available IConfigurationManager<OpenIdConnectConfiguration> configurationManager; OpenIdConnectConfiguration openIdConfig = null; bool metadataAvailable; try { configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Jwt.MetadataAddress, new OpenIdConnectConfigurationRetriever()); openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result; metadataAvailable = true; MetadataStatus = $"Successfully loaded metadata."; } catch (Exception e) { MetadataStatus = $"Failed to load metadata (skipping signature validation): {e.Message}"; metadataAvailable = false; }

 

 

 

 

Step 3 - Set up token validation parameters

As said already we handle the absence of metadata gracefully. We can still check things like the lifetime and the audience, but we are not able to verify the signature. And that is the critical part - what separates my fake tokens with actual Azure AD tokens is the keys they are signed with. Which is why you should never disable validation of the signing key like we do here :)

 

While there is some logic in locating the keys and retrieving them we see that this is one of the things the libraries handle for us in a developer friendly way.

 

 

 

 

TokenValidationParameters validationParameters = null; //If we cannot load metadata we fall back if (!metadataAvailable) { validationParameters = new TokenValidationParameters { ValidIssuer = Jwt.Issuer, ValidAudience = Jwt.Audience, ValidateLifetime = true, ValidateAudience = true, ValidateIssuer = true, //Needed to force disabling signature validation SignatureValidator = delegate (string token, TokenValidationParameters parameters) { var jwt = new JwtSecurityToken(token); return jwt; }, ValidateIssuerSigningKey = false, }; } //If we succcessfully loaded metadata we do signature validation as well if (metadataAvailable) { validationParameters = new TokenValidationParameters { ValidIssuer = openIdConfig.Issuer, ValidAudience = Jwt.Audience, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidateAudience = true, ValidateIssuer = true, IssuerSigningKeys = openIdConfig.SigningKeys }; }

 

 

 

 

Step 4 - Validate the token

The actual validation is shorter than the setup. We read the JWT. (Notice how Microsoft also use "token" twice in the naming - spelling out the acronym it would be ReadJsonWebTokenToken.)

 

 

 

 

token = handler.ReadJwtToken(Jwt.Base64Token); try { var identity = handler.ValidateToken(Jwt.Base64Token, validationParameters, out SecurityToken validatedToken); if (metadataAvailable) { output += "Token is valid according to metadata!"; } else { output += "Token is valid according to a self-evaluation!"; } jwtSignature = token.RawSignature; } catch (Exception e) { //Print errors }

 

 

 

 

Step 5 - Pretty print the contents and the result

The last step is to print out the result of the validation test, and the contents of the token split into header/payload/signature with some color coding. I did not find satisfactory ways to do this with the built-in mechanisms as these didn't really play nice with Blazor so I did a more hackish and manual approach for this. Not including the code here, but it is of course part of the sample on GitHub.

 

ValidationResult.png

 

In an actual app or api you would of course not care so much about these things anyway.

 

While I also prefer using "proper" libraries for validation I use tools like this all the time when working with things like custom policies in Azure AD B2C.

 

I'm sure you are able to come up with more elaborate versions that what I did here, but I'm hoping it provided some of the basic understand for making sure you don't end up in the category of developers who skip token validation. (There are unfortunately too many that don't do this right and produce code with horrible vulnerabilities included.)

Leave a Reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.