# Hasura JWKS: JWT w/ Multiple Auth0 tenants

TL;DR - By default, Hasura does not accept multiple JWKS URLs. Thus, write a lambda or a simple HTTP API to combine the JWKS keys of multiple tenants into a single JWKS endpoint. (Bonus: Buggy Auth0 fingerprint implementation)

### Prelude

Before starting, please make sure to add the Auth0 Rule as mentioned [in this support document](https://hasura.io/docs/latest/graphql/core/guides/integrations/auth0-jwt.html). Test to make sure the Hasura Claim is present in the JWT issued by Auth0.

### The Auth0 JWKS Bug

Thanks to a [pretty weird bug](https://community.auth0.com/t/certificate-thumbprint-is-longer-than-20-bytes/7794/3) in Auth0, if you have an old signing key generated on/before April 2020, the key has an invalid x5t fingerprint. Thus, Hasura will not work if you point it to a JWKS endpoint containing the old x5t fingerprints.

Thankfully, Auth0 has issued a fix, and all you need to do is [rotate your signing keys](https://community.auth0.com/t/jwk-certificate-thumbprint-is-invalid/16070/22).

### Observing the JWKS response

The JWKS URL of your Auth0 Tenant will respond resembling the type declaration stated below.

(Hint: `https://..auth0.com/.well-known/jwks.json` )

```typescript
export interface Key {
    alg: string;
    kty: string;
    use: string;
    n: string;
    e: string;
    kid: string;
    x5t: string;
    x5c: string[];
}

export interface RootObject {
    keys: Key[];
}
```

Thus, we just need to fetch the JWKS Key objects from all our tenants and merge them into a single keys array. Let's do it.

### Building the Lambda

Let's list down all our JWKS endpoints in an array. Make sure to replace it with your Auth0 Domains.

```javascript
const jwksUrls = [
    'https://foo.au.auth0.com/.well-known/jwks.json',
    'https://bar.au.auth0.com/.well-known/jwks.json',
    'https://baz.au.auth0.com/.well-known/jwks.json',
];
```

Next, time to parallelly download all the JWKs for merging. We'll be using `axios`.

```javascript
const axios = require("axios");

const responses = await Promise.all(jwksUrls.map((url) => axios.get(url)));
const keySets = responses.map(response => response.data.keys)
```

Okay, we have to take a break here. Recall the Auth0 x5t bug? Yeah. It turns out, even if you rotate the key, for the sake of other microservices in your system, you may decide to keep the previous key still valid. Otherwise, the previously issued tokens would become invalid and your users wouldn't stay "logged in" anymore.

There is a tiny workaround. It is to remove the invalid keys with the invalid x5t signatures from the `keySets`. However, this does mean that your new Hasura system will only work with newly issued JWT tokens signed with your shiny new key. Thus, we will replace that last line with,

```javascript
const keySets = responses.map(response => response.data.keys.filter(key => key.kid !== key.x5t));
```

Because in the faulty implementation, the kid and x5t are the same.

### The final code for the Lambda

```javascript
const axios = require("axios");

exports.handler = async (event) => {
    const jwksUrls = [
        'https://foo.au.auth0.com/.well-known/jwks.json',
        'https://bar.au.auth0.com/.well-known/jwks.json',
        'https://baz.au.auth0.com/.well-known/jwks.json',
    ];

    const responses = await Promise.all(jwksUrls.map((url) => axios.get(url)));
    const keySets = responses.map(response => response.data.keys.filter(key => key.kid !== key.x5t));
    const keys = [].concat(...keySets);

    const response = {
        keys,
    };

    return response;
};
```

### Pointing Hasura to the JWKs endpoint

You may choose to create a lambda or host this function any way depending on your preference. Finally, once you have the endpoint to the JWKS merging API, simply point Hasura to it. Update the `HASURA_GRAPHQL_JWT_SECRET` environment variable to,

```json
{
  "type":"RS256",
  "jwk_url": "",
  "claims_format": "stringified_json"
}
```

The end!
