Skip to content
This repository has been archived by the owner on Oct 29, 2021. It is now read-only.

Support updating JWKSet #153

Open
axman6 opened this issue Aug 6, 2019 · 7 comments · May be fixed by #169
Open

Support updating JWKSet #153

axman6 opened this issue Aug 6, 2019 · 7 comments · May be fixed by #169
Assignees

Comments

@axman6
Copy link

axman6 commented Aug 6, 2019

We have a service which periodically needs to update the set of known keys which will be used to sign requests. The simplest way I can see to allow this is to have

validationKeys :: JWKSet

become

validationKeys :: Either (IO JWKSet) JWKSet

or something isomorphic to this. For our particular use case this happens frequently enough that it would be better to not have to shoot the service in the head to periodically fetch the latest keys.

@lachezar
Copy link

This could be quite useful feature in case you chose to use authentication through JWT issued by Firebase Auth or Auth0's, since they rotate their JWKs periodically.

@jkarni
Copy link
Member

jkarni commented Aug 5, 2020

Makes a lot of sense. I'll make the changes.

@jkarni jkarni self-assigned this Aug 5, 2020
@jkarni
Copy link
Member

jkarni commented Aug 5, 2020

This could be quite useful feature in case you chose to use authentication through JWT issued by Firebase Auth or Auth0's, since they rotate their JWKs periodically.

@lachezar I'm only familiar with AWS Cognito. Do Firebase and Auth0 have a URL with the JWK set encoded as JSON as well? Is the URL usually public? Do you know if their cache headers state how often they rotate? If so, it might make sense to write a helper function that does all of the setup for Cognito/Firebase/Auth0 in one go.

@lachezar
Copy link

lachezar commented Aug 5, 2020

@jkarni yes, they have a public url with their JWK set, however they also have different formats from what I have read about the topic from Hasura's docs - https://hasura.io/docs/1.0/graphql/manual/auth/authentication/jwt.html#popular-providers-and-known-issues

I have not seen any official information how often Google rotate the keys for Firebase, but from empirical tests (refreshing the endpoint https://www.googleapis.com/service_accounts/v1/jwk/[email protected] for their JWK set in the browser every now and then) it seems the key is swapped once a week.

I think the refresh duration could be a configuration option in the JWKSettings and this would allow the users to work around all possible cases.

@jkarni jkarni linked a pull request Aug 5, 2020 that will close this issue
@erewok
Copy link
Contributor

erewok commented Aug 5, 2020

Do Firebase and Auth0 have a URL with the JWK set encoded as JSON as well? Is the URL usually public? Do you know if their cache headers state how often they rotate? If so, it might make sense to write a helper function that does all of the setup for Cognito/Firebase/Auth0 in one go.

I can offer another example and say that these are all true for Okta as well.

However, on Okta you can set up different "Authorization Servers" which will sign the JWT. In other words, while the URL for the JWKs is public, it's dependent on which server you use (but I imagine that you probably wouldn't use more than one auth server in a single project?). In addition, Okta recommends caching these for a short duration:

Okta also recommends caching or persisting these keys to improve performance. If you cache signing keys and automatic key rotation is enabled, be aware that verification fails when Okta rotates the keys automatically. Clients that cache keys should periodically check the JWK for updated signing keys.

For Okta, they also include a Cache-Control header in the response to a request for the JWK.

In short, for our projects relying on Okta, we would retrieve the JWK from a cache or by issuing a GET request against the auth server before validating a JWT on each request.

@jkarni
Copy link
Member

jkarni commented Aug 5, 2020

Thanks @erewok. AWS Cognito seems to cache for ~ 24h.

In short, for our projects relying on Okta, we would retrieve the JWK from a cache or by issuing a GET request against the auth server before validating a JWT on each request.

This would be the easiest to implement. On the other hand that likely adds at least 10ms per request, which is quite a lot. Since it seems most identity providers do set cache headers, a better approach would be to cache based on that. Sadly, it doesn't seem like there's any haskell http client that allows configuring a cache (but does that processing of the various cache headers automatically)!

The alternative is to keep an IORef/MVar/etc with the key, which is where authentication reads from. And then either fork a thread to periodically check for updates, or check for an update every time a request comes in that is signed by a key that isn't in that ref.

@erewok
Copy link
Contributor

erewok commented Aug 5, 2020

In the past, I have put them in redis with a TTL larger than the cache control header suggests and then I have always optimistically pulled from the cache while falling back to a GET if the kid in the JWT header doesn't match those that I have in the cache. (This is the latter suggestion you have there).

Thanks for working on this, by the way. I experimentally tried to use servant-auth to do something like this just as an example a couple of months ago, and I gave up pretty quickly.

Years ago, we had a reverse-proxy based on OpenResty that used this auth0-lua-nginx project to do all of this before forwarding requests upstream and that was pretty fancy and nice because each project only needed to check scope or something and no longer needed to validate the JWT.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants