For practicality, you should rotate your keys based on the impact it has on the security and the user experience. It also depends on your implementation.
The actors are
- client
- resource provider (RP)
- identity provider (IP) implementation detail... there's no reason an IP and RP cannot be the same entity, that's how it was before all these OIDC stuff. but it is still good to think about it in those terms in case you want to scale or use alternate OIDC providers)
Let's assume your implementation is that your access tokens are Json Web Signature (JWS) content of your JWT claims. The JWT in access token approach primarily replaces the notion of JSESSIONID along with the storage of such information in the backend and forwarding that storage responsibility to the client.
From the client perspective:
If the client has no OAuth token it does some process (e.g. OIDC) to get the OAuth token
If the client has an OAuth token it sends the access_token
as the bearer to the RP
If the RP responds with 401 or the client thinks the RP will respond with a 401.
- the client will use the refresh token endpoint to get a new token from the IP
- if the IP responds in error, the refresh process failed and the user is logged out
- else continue
Else just standard RP response
The 3rd step specifies the refresh token process. This needs to be noted as that also needs to be factored in when determining the rotation policy.
Given the scenario in the quoted section, there's only one party that would really need to care about the signature, the one that is consuming the JWT for the purpose of identifying the user and authorization level data, that's the RP
The JWT in the bearer token would be associated to a specific JWS, and the JWT can be reused until the time expires. Since this happens often the RP needs to have some layer that does this validation quickly. It does this by having the public key of the signer, that is provided by the IP. On the OpenID Connect (OIDC) standard it is referenced by the jwks_uri
of OpenID Discovery. Regardless on how the RP retrieves it, the RP must have a trusted way of getting the key from the IP.
Let's put in some variables now
exp
access token expiration TIME not duration
jwke
JSON web key expiration TIME not duration
njwk
number of active JWKs
maxjwke
JSON web key expiration DURATION
maxexp
access web key expiration DURATION
Given that jwke
>= exp
otherwise the client will get a 401 unexpectedly because the key is no longer valid for their access token.
Now for simplicity you can have a JWK per access token, that would work, and relatively secure, but will kill your backend as it's just creating crypto tokens all the time. But that's a minimum.
So at the very least I would have a reasonable set of JWKs as a pool, could be 10 could be 100 it depends on your tolerance.
Now what's the maximum? Well that depends on your tolerance and cost of creating the tokens.
But regardless of your tolerance values the one thing you need to ensure is for a given JWK it should NOT expire before the access time.
IP Implementation notes
Now let's say you're implementing in REDIS which has two key limitations:
- You cannot SCAN across cluster nodes
- Inability to expire hash elements
You can implement it as follows
- create hash pairs of
njwk
elements containing the public JWK and private key (stored as encoded bytes)
- each hash pair will be keyed based on some time block say day of the week assuming that
maxexp
will not go past maxjwke
.
- each hash pair will have a default expiration of
maxjwke
- when key is used for an access token, it will extend the expiration ONLY OF THE PUBLIC KEY such that it is
maxjwke
+ remaining access token expiration time. (this bit ensures that the access token will work within the access period it has. The private key is is still set to expire.
- when providing the list of JWKS it should provide current and last set so long as it hasn't expired.
- when choosing the key to sign with it should choose from a the current bucket which has not expired.