If you create an application or API that is secured with Azure AD, you are likely going to require a consumer of your application to provide an OAuth access token in order to access your application or API. The caller would have to obtain this token from Azure AD by first authenticating with Azure AD and then request a token for your application.
For the rest of this post, I'm going to assume you are working with a REST API, but everything applies to an application as well.
But anyone can create an OAuth access token. It's just a JSON object that has a set schema and then base64 encoded. There's nothing secure about it.
If you've elected to use Azure AD to secure your REST API, you have established a trust with Azure AD. Therefore, when you receive the OAuth access token from the caller, you should first validate two things:
- This token was generated by Azure AD & its contents have not been altered
- This token is intended to be used only by "me"
Validating the intended audience
The second part of this validation process is very simple. You need to check the audience part of the JWT token. This is listed as the
aud property and it contains the URI of the audience the token is intended for.
Confused? Think of this like the street address of your home. A key used to unlock your front door has this address on it. A key with a different address should not be "validated" and allowed to open your front door because it is intended to be used with someone else's front door (aka: a different audience).
You can ensure the audience property in the token was set by Azure AD because you previously validated that the token was generated by Azure AD and it's contents have not been altered.
Wait... we skipped that part... let's jump back...
Validating Azure AD JWT tokens
So... how you do validate an Azure AD JWT token? A JWT token contains three sections:
- header: specifies the algorithm used to digitally sign the token & type of the token
- payload: the data in the JWT token... what we want to work with
- verification signature: this part contains the digital signature of the token that was generated by Azure AD's private key.
The way you validate the authenticity of the JWT token's data is by using Azure AD's public key to verify the signature. If it works, you know the contents were signed with the private key. If not, you can't be sure of it so you should treat the JWT token as an invalid token.
So... back to the question: how you do validate an Azure AD JWT token?
You first need to obtain the Azure AD public key. To do this, start by calling the public Azure AD OpenID configuration endpoint: https://login.microsoftonline.com/common/.well-known/openid-configuration.
Within the JSON response, you'll see a property jwks_uri which is the URI that contains the JSON Web Key Set for Azure AD. For Azure AD, that URI is https://login.microsoftonline.com/common/discovery/keys.
When you go there, you'll see an array of keys. Each key has a set of properties. I'm only interested in a few of these, but you can learn about all properties here if you're interested.
What you're looking for is a key that has the same thumbprint of the x.509 certificate (SHA-1 thumbprint) that's listed in the header of the JWT token you got from Azure AD. How do you get that?
I'm going to use the site https://jwt.io to easily decode a real JWT token I got from calling the Microsoft Graph. Look at the header value:
kid property, I can tell that's the key I'm looking for. So going back to the JSON Web Key Set URL for Azure AD, I'll find the matching key.
Once you find the key, take the value and wrap it in the begin & end certificate markers:
-----BEGIN CERTIFICATE-----<newline><key><newline>-----END CERTIFICATE-----.
This new string is what you can use as the public key to validate a JWT token. For instance, using the NPM package jsonwebtoken, you can do it like this:
If no errors were thrown and you got a token back, you have yourself a validated JWT token that you can trust was created by Azure AD and has not been tampered since Azure AD generated it!
If errors were thrown, you can check for specific issues with the validation process, as documented in the jsonwebtoken NPM package: Errors & Codes