Aug 16, 2022

Announcing JWT Scopes: Create Cache Buckets Based on JWT Claims

Blog post's hero image

Today we’re excited to announce that Stellate’s GraphQL Edge Cache now supports scoping cached responses by individual JWT claims!

Scoping cached responses by a JWT claim will allow your users to still get cache hits, even when their JWT token is refreshed. On top of that, groups of your users that have access to the same private data can share a cache bucket rather than having individual cache buckets, which can give you a much higher cache hit rate.

Already itching to start using this? Check out the docs on how to configure JWT scopes!

The Power of Scoping Cached Responses by JWT Claims

For example, imagine a members-only section of a forum that uses JWTs as their authentication token format. The public doesn’t have access to any of the posts that happen in the members-only section, yet all the members see the exact same posts when visiting that section.

When caching the post data scoped to the whole JWT token, which was the only way to add caching to that private data before, every time you refresh the JWT the members would stop getting cache hits (even though the data might still be in the cache) and every member would get their own cache bucket. When a member visits the members-only section for the first time the first query would always be a cache MISS, even if thousands of other members have just looked at the exact same members-only section and loaded the exact same data.

Our Scopes feature can now scope cache buckets based on JWT claims. Instead of having to use the whole token for defining cache buckets, you can scope the cached data based on any individual claim within the payload of the JWT.

Going back to our example, that means a member that just refreshed their JWT will still get cache HITs, and it allows the cache for the members-only section to be shared between all the members. Once a single member has loaded the members-only section, every other member opening it while the data hasn’t changed is going to get a cache HIT! 🎉

Check out the docs to learn how to set up JWT scopes!

What are Scopes again?

Imaging that you have a GraphQL query that fetches the data for the currently logged-in user. It could be as simple as sending the query { me { name } }. The GraphQL request will be the same for all users, but the responses are different for each individual user. This is a problem when it comes to caching.

The naive caching approach of storing and reusing the result of the first request would have severe consequences. This would mean that the second request - which might come from a different user - will get the same response as the first user did. In other words, we’re leaking private data!

Stellate has a feature called “Scopes” that solves exactly this problem. Instead of having one cache bucket where all responses end up, you can define a “Scope” that will split the cache into multiple buckets, where each bucket will contain only the response data for a specific scope value.

Coming back to our example, usually, you are using a cookie or the Authorization header to send a token that uniquely identifies a single user. So the GraphQL Edge Cache allows you to define Scopes based on exactly that, cookies, and headers.

What’s special about JWTs?

JSON Web Tokens are a special token format that's commonly used for authentication and authorization. It consists of three parts:

  • A header that contains metadata

  • A payload that information about the user and the token lifetime (JWTs have an expiration date where they become invalid)

  • A signature that can be used to verify that the JWT has actually been created from the same authority that receives it

The payload is the interesting part here as it usually contains the unique identifier for a user (like an id, email, etc). And given that each JWT only is valid for a certain time, there are often multiple tokens that all relate to the same user.

This is where the Scopes feature breaks down. If we’d only look at the value of the whole token, then each token would create its own cache bucket. For a user, this sucks. Imaging that each time you re-authenticating you have to wait for a couple of long-running queries just because you got an empty cache. Wasted time!

Enter JWT Scopes

We just shipped the next version of our Scopes feature that addresses these shortcomings! Instead of using the whole token for defining cache buckets you now get more fine-grained control:

  • You can mark any Scope definition as “JWT Scope”

  • You can choose which part of the token payload (commonly called “claim”) should be used to define cache buckets

  • We support all common encoding algorithms (both symmetric and asymmetric)

To get started, head over to the config of your Stellate service. If you already have a scope defined it should look something like this:

const config: Config = {
"config": {
"scopes": {
"AUTHENTICATED": "header:Authorization"
},
// ...
}
}

Now all you need to do is to replace the definition string with an object like this and add the jwt property:

const config: Config = {
"config": {
"scopes": {
"AUTHENTICATED": {
"definition": "header:Authorization",
"jwt": {
// Which property from the payload to choose for creating cache buckets (even supports nested properties using JSON-path)
"claim": "sub",
// The algorithm that you use to sign your JWTs
"algorithm": "HS256",
// The secret or public key used for signing
"secret": "shhh"
}
}
},
// ...
}
}

Now go and take your cache hit rate to the next level! And if you don’t have a Stellate service yet, then it’s past time you sign up! (It’s free!) Head over to stellate.co and enjoy JWT scopes!