JWT.io signature validation
Published: 2022-02-09 15:59:51

Have you ever wondered how [jwt.io](https://jwt.io/) gives you the Signature Verified badge when you paste a new JWT?
<p align="center">
<img  src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tu7fxk1b37z4a553tm6i.png" alt="Signature verified with a tick from jwt.io" width="150"/>
*Note: This only works for asymmetric signing algorithms such as RS256 signed JWTs.*

The JWT was signed using a private key which is safely inside the issuer but there is a public key available so that any recipient of the token can validate if it is valid or not.

## Introduction
This blog is focused on using a familiar and popular tool as a worked example for how automatic signature verification of JWTs can happen. It is not about recommending you to or not to use tools like this. I do however want to pause for a second to remind you that [jwt.io](https://jwt.io/) is an online tool. It does state that it runs in the client, but as ever you should never automatically trust online tools and always consider the risk / threat they pose. 

As a minimum, I would suggest never putting production tokens into sites like this (or anywhere you don't control) and recommend you find a locally runnable alternative. 

[Jamie Tanna](https://www.jvt.me/) who is a member of my favorite API community ([APIs you wont hate](https://apisyouwonthate.com/community)), wrote a fantastic blog actively discouraging the use of tools like this: [Why I Actively Discourage Online Tooling like jwt.io](https://www.jvt.me/posts/2020/09/01/against-online-tooling/). This might be a strange link in a blog that explains how this tool works but it is a popular tool that many take for granted and has some interesting functionality to share.

Hopefully the introduction has not put you off (that was not the intention). If not, lets explain how it works.

## How does it work?

Inside the decoded JWT payload is the `iss` property (Issuer of the token). This will be a URI that can be used to find the **well-known endpoint** and from there the `jwks_uri` which is the location where the public key(s) can be retrieved.

Important note: JWTs are untrusted input so you should not directly trust what is located inside! This means if you are relying on the issuer to retrieve the public key, make sure you are validating the issuer before calling it to retrieve a public key. It might not be your issuer! Similarly the token could tell you where the public keys are via the `jku` parameter to send your verify function to retrieve keys an attacker controls.

Below is an example of an access token that is a JWT in encoded and decoded form using https://jwt.io

<p align="center">
<img  src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2tcwndlp23mym86oxav.png" alt="An example JWT from https://jwt.io in encoded and decoded form" width="500"/>

## What is a well-known endpoint?

Thanks to [RFC8414 - OAuth 2.0 Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414), authorization servers provide a **well-known** endpoint that returns information about how to interact with the service at: <p>
 `http://service-url.com/.well-known/openid-configuration`

It returns information such as the OAuth token and authorization endpoints, which OAuth grants are available, scopes and many more. In order to retrieve the public keys we need to validate our token, we need the **jwks_uri**
```
// Example response from a well-known endpoint provided by Auth0.com
// Some items are removed to reduce the overall height of this code block
{
  "issuer": "https://dev-spu08zrz.us.auth0.com/",
  "authorization_endpoint": "https://dev-spu08zrz.us.auth0.com/authorize",
  "token_endpoint": "https://dev-spu08zrz.us.auth0.com/oauth/token",
  "jwks_uri": "https://dev-spu08zrz.us.auth0.com/.well-known/jwks.json"
  "scopes_supported":
    [
      "openid",
      "profile",
      "offline_access",
      "name",
      "email"
    ],
  "code_challenge_methods_supported": ["S256", "plain"],
  "response_modes_supported": ["query", "fragment", "form_post"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["HS256", "RS256"],
  "token_endpoint_auth_methods_supported":
    ["client_secret_basic", "client_secret_post"],
}
```

## jwks_uri

The jwks_uri returns the JSON Web Key Set (List of JSON web keys). There is often more than one to allow for key rotation. Each key in the set contains the public key that can be used to verify the signature of a the JWT.

[Auth0 has a detailed write up on JSON Web Keys here](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets)

Here is an example response from a jwks_uri. You can see there are 2 keys currently available. It contains the required information for a token verifier to locate the correct public key that matches the `kid` (key id) from the token header and pass it to the verifying function.
```
// Key length has been shorted to make this code block more readable.
{
  "keys": [
    {
      "alg": "RS256", // This is the algorithm that was used
      "kty": "RSA", // This is the key type: RSA being asymmetric
      "use": "sig", // This is the usage: sig = signing, enc = encrypting
      "n": "xi9SZLDzHULsG_ab9zBO2....", //This is the modulo of the public key
      "e": "AQAB", // This is the exponent of the public key (used with the modulo)
      "kid": "uMSsz8nx8OEuXhEbnXcoH", // This is the Key Id needed to match the header
      "x5t": "k1uZJUy2G7i-acZ36Yg....", // This is the thumbprint of the x509 cert
      "x5c": [
        "MIIDDTCCAfWgAwIBAgIJEd...." // This is the x509 certificate chain
      ]
    },
    {
      "alg": "RS256",
      "kty": "RSA",
      "use": "sig",
      "n": "yBMPFvevpfesWmtdmt8V....",
      "e": "AQAB",
      "kid": "oHf3mAsLQ2vaXgxMRP0qh",
      "x5t": "jN_H055zOjrXkQSdiunn....",
      "x5c": [
        "MIIDDTCCAfWgAwIBAgIJej6...."
      ]
    }
  ]
}
```
## Verifying JWTs

Using the kid and alg in the JWT header (JOSE Header from [RFC7515 - JSON Web Signature (JWS)](https://datatracker.ietf.org/doc/html/rfc7515)) and the public keys from the authorization servers jwks_uri, we can verify the signature. 

<p align="center">
<img  src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zpjw7mh641s47ruom0j6.png" alt="An example JWT's JOSE header from https://jwt.io in decoded form. It contains the details of how the token is signed, its type and which key was used to sign it." width="400"/>

To verify the signature, [jwt.io](https://jwt.io/) uses the JWK’s: key id, e and n values which represent the public key.
```
{
    "alg": "RS256",
    "kty": "RSA",
    "use": "sig",
    "n": "xi9SZLDzHULsG_ab9zBO2b1L...",
    "e": "AQAB",
    "kid": "uMSsz8nx8OEuXhEbnXcoH",
    "x5t": "k1uZJUy2G7i-acZ36YgCw6...",
    "x5c": [
        "MIIDDTCCAfWgAwIBAgIJEd8o..."
    ]
}
```

If you want to manually verify signatures with [jwt.io](https://jwt.io/), you will need to paste in a JWK (as above) into the box in the bottom right (normally automatically completed) and it will verify the signature. 

### What about PEMs?
Some verify functions such as the popular [Auth0 node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js) library requires the public key in PEM format. There are many libraries available to do this conversation.
<p>
Here is an example of a JWK converted into PEM format: (Its not a real one)

```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkBAQEFAAOCAQ8AMIIAQEAi3gtrfA
MtCEFTqRWF9wxfnGA2VYVsI7jByBK7/ulIdHI64B2
IQgct0hLRPcPFHOhBxjwa8Ls7pvNyUPD4srMrzW27
EVeShS09Z5F4/tBWFpxzi+Cdpd8mKAqJyQYebgfBE
Kv61IrhNOPRypWJG40+YTO5rDAkKsF0HXZ+beuabD
vWiIwV5A76866FOn3PKL73yTdGEjiXuB7o9Aak8k/
2wIDAQAB
-----END PUBLIC KEY-----
```

## Summary
If you are using JWTs today you have probably come across JWT.io, but perhaps have never given a second thought to how the validation works. I hadn't, but found it interesting and felt the need to share with others in case they did too.

Perhaps you are using them today but had not looked into the detail of the various components you are or could be using.

I hope you found this helpful to gain some more knowledge but as always, be careful and keep your applications safe. They are many ways to open up security holes by misconfiguration or out of date libraries. Do your research and lots of testing.