Hasura JWKS: JWT w/ Multiple Auth0 tenants

A Software Engineer passionate about public good projects and making technology to serve society en masse.
Search for a command to run...

A Software Engineer passionate about public good projects and making technology to serve society en masse.
No comments yet. Be the first to comment.
HS (Harmonised System) Codes help classify customs authorities and businesses around the world to ensure appropriate import/export control. This system recursively classifies a product into finer granularity of categories. Figure 1: A simple exercis...
Jan allows you to deploy and run LLMs on your Windows or macOS computer! 16 Gigabytes of memory and a M1 chip was enough for me to run Mistral locally. Now how do you take Jan and use libraries like Langchain? https://jan.ai Local API Server Click ...
My talk at PyCon Thailand 2023 on the security aspects and possible attack vectors of LLM Applications, tailored to both product people and engineers! https://youtu.be/_fvk_Qa5OsM Thank you PyCon TH team!
Looking for a flat for rent, with LLMs
Code generators replace mindless coding with productivity; a pure function that produces predictable code, given a (set of) input(s). Generating boilerplate classes or templates — a common use case for Codegen. Use-case: Generating Classes We will be...
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)
Before starting, please make sure to add the Auth0 Rule as mentioned in this support document. Test to make sure the Hasura Claim is present in the JWT issued by Auth0.
Thanks to a pretty weird bug 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.
The JWKS URL of your Auth0 Tenant will respond resembling the type declaration stated below.
(Hint: https://..auth0.com/.well-known/jwks.json )
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.
Let's list down all our JWKS endpoints in an array. Make sure to replace it with your Auth0 Domains.
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.
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,
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.
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;
};
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,
{
"type":"RS256",
"jwk_url": "",
"claims_format": "stringified_json"
}
The end!