Validating JWT from multiple identity providers

I have been tasked recently with finding a way to validate JWT that can come from multiple different identity providers, in the same API. This tends to happen when companies buy each other and try to merge their products and user base. So, first, let's be clear about one thing: wrangling multiple IdPs is the path of pain. You would think that it would avoid painful migrations and get you up and running quickly, but you will pay a huge complexity tax when juggling users and roles from multiple systems interacting in your application.

Validating the JWT, though, is manageable, but I've not seen good advice on how to verify them when they were produced by multiple identity providers. So, let's find a good process for that.

First, some quick terminology if you're not familiar with them:

Why is it hard to verify a JWT?

A lot has already been written about the pitfalls of JWT verification. They boil down to: you should not trust what the token tells you about its signature algorithm. There were multiple flaws due to token coming in with the "none" algorithm, which libraries happily understood as a token they don't need to verify (most libraries have fixed that now), or more subtle ones, where a token references a RSA key but with the HMAC algorithm, which results in the library verifying that HMAC using the RSA public key.

So you should not trust the token, but the specification does not help you here, and the behaviour of some identity providers does not help either.

Let's get into the details! A JWT has a JOSE header that contains parameters about the signature algorithm:

So in theory, we should take the kid, look up the key we want to use, then make sure the alg from the token matches the alg from the key, and then verify the token.

Now let's look at the JWKS content. It contains an array of key objects, and each object contains:

Some additional context on what an IdP could do with a JWKS:

Matching these with what the JWT provides is indeed a challenge. An advice I've seen here and there is to restrict algorithms to a reasonable set, like RS256 and HS256, but that won't be enough if the JWKS contains keys of both RSA and oct types with the same kid.

And the fun is not over: now you need to get a JWKS from each of multiple providers! Is it possible that different identity providers give keys with colliding kid and kty? In theory, yes, but I've not seen it yet, because large IdPs tend to generate random looking kid. It's entirely possible that a homegrown IdP would generate incrementing kid though.

How do we match a JWT to a key?

Since we may not have a direct match, we need a deterministic process to choose the key, and make sure there is no algorithm confusion. here is the process we came up with for the Apollo Router:

Hopefully, at this point you would either have no matching key, and you refuse the token, or you have a matching keys. It is still possible to get multiple candidate keys, due to the specification's (lack of) constraints. But at this point the keys you have chosen are safe to test, and if one of them verifies the token, then it's ok.

There is one final step after that: check that the issuer claim iss in the deserialized JWT, if present, matches the issuer of the JWKS. If it is not, you should ask your IdP what they are doing.

I'm not too happy about it, it is a complex process that could have been avoided if:

If you wonder how this is handled with Biscuit tokens: