JSON Web Keys are what is used to generate and verify JSON Web Tokens. Here I will explain how best to rotate JWKs.
If you’re coming from zero like me and building something that uses JWT to verify a request is what it should be then the first thing you’ll do is use a JWK to verify a JWT. Using the github.com/lestrrat-go/jwx library in go it can be done like this:
parsedKey, err := jwk.ParseKey(key)
parsedToken, err := jwt.ParseString(token, jwt.WithKey(jwa.HS256, parsedKey))
However now any time you change the key you’re going to run into problems where it will be a hard break. So now the old token will stop working straight away and the new token will be accepted without any overlap period between. This is a problem for APIs as now you’re going to have a whole load of failed requests before you can get the new token to each of them. Sure this may be okay if you think you can roll it out quickly enough or you can add some extra code to the clients but this extra complexity is not really what we want.
Instead it’s better to use JSON Web Key Sets (JWK Sets) which are arrays of JWKs like the example below. You can see we have two keys with different key (k
) and key ID (kid
) values to differentiate them. Any keys in the set will be accepted so now when you are rolling a token, you can just update the set to have both the new and old JWKs. Once you’re finished updating each client to the new JWK you can remove the old one.
{
"keys": [
{
"kty": "oct",
"use": "sig",
"alg": "HS256",
"kid": "v1",
"k": "abc"
},
{
"kty": "oct",
"use": "sig",
"alg": "HS256",
"kid": "v2",
"k": "def"
}
]
}
To use JWK Sets it is similar to the example above only now we’re using jwk.Parse()
which returns a jwk.Set
. This is passed into the jwk.ParseString()
which returns the parsed token if it matches one of the keys in the set.
set, err := jwk.Parse(keys)
parsedToken, err := jwt.ParseString(token, jwt.WithKeySet(set))