Fix ID token and session token JWK conflcit in KV store#32
Open
valeriangalliat wants to merge 1 commit into
Open
Fix ID token and session token JWK conflcit in KV store#32valeriangalliat wants to merge 1 commit into
valeriangalliat wants to merge 1 commit into
Conversation
Fixes Code-Hex#31 The current implementation uses a signle `WorkersKVStore` for the user-provided KV key. Both the ID token verifier and session cookie verifier use this same store, but they each need a different JWK. The ID token verification keys are fetched from <https://www.googleapis.com/robot/v1/metadata/jwk/securetoken@system.gserviceaccount.com> while the session cookie verification keys are fetched from <https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys>. Whichever one gets used first gets cached in the KV store, then both ID token and session cookie verifiers will use those same cached keys until TTL expires. This means one of them will get the wrong keys and reject all verifications. This PR fixes this. I tried to go with the approach that is the least breaking possible. I didn't want to change the public interface of `Auth` nor `WorkersKVStore`. Instead this PR adds a `scoped(scope: string)` function to `KeyStorer` (optional for backwards compatibility), and implements it in both `WorkersKVStore` and `WorkersKVStoreSingle`. It returns an instance of the same class with `:${scope}` appended to the original key. In the case of `WorkersKVStoreSingle`, `.scoped()` also registers a singleton for that scope. Then in `BaseAuth`, we scope the `KeyStorer` in the contructor: ```js function scopedKeyStorer(keyStorer: KeyStorer, scope: string): KeyStorer { if (typeof keyStorer.scoped === 'function') { return keyStorer.scoped(scope); } return keyStorer; } this.idTokenVerifier = createIdTokenVerifier(projectId, scopedKeyStorer(keyStore, 'id-token')); this.sessionCookieVerifier = createSessionCookieVerifier(projectId, scopedKeyStorer(keyStore, 'session-cookie')); ``` Note: the `scopedKeyStorer` function is used in case the user passes a custom `KeyStorer` that doesn't have the `scoped` method we just added. Then it'll work just like before with the ID token and session cookie conflict. Upgrading to this patched version will result in the previous cache key being ignored, and the appropriate keys to be fetched in the new scoped stores on first use. The previous cache key will stay in the KV until its TTL, then it'll be garbage collected without any manual cleanup necessary.
740b585 to
3f744d5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #31
Overview
ID token verifier and session cookie verifier use different and incompatible signing keys in the same cache entry.
Verifying an ID token prevents verifying session cookies and vice versa.
This PR adds a
.scoped(scope)function to the store that adds a:id-tokenand:session-cookiesuffix to the provided cache key, so that both verification methods can work at the same time.Problem
The current implementation uses a signle
WorkersKVStorefor the user-provided KV key. Both the ID token verifier and session cookie verifier use this same store, but they each need a different JWK.The ID token verification keys are fetched from
https://www.googleapis.com/robot/v1/metadata/jwk/securetoken@system.gserviceaccount.com while the session cookie verification keys are fetched from https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys.
Whichever one gets used first gets cached in the KV store, then both ID token and session cookie verifiers will use those same cached keys until TTL expires. This means one of them will get the wrong keys and reject all verifications.
Solution
This PR fixes this. I tried to go with the approach that is the least breaking possible. I didn't want to change the public interface of
AuthnorWorkersKVStore.Instead this PR adds a
scoped(scope: string)function toKeyStorer(optional for backwards compatibility), and implements it in bothWorkersKVStoreandWorkersKVStoreSingle. It returns an instance of the same class with:${scope}appended to the original key.In the case of
WorkersKVStoreSingle,.scoped()also registers a singleton for that scope.Then in
BaseAuth, we scope theKeyStorerin the contructor:Note: the
scopedKeyStorerfunction is used in case the user passes a customKeyStorerthat doesn't have thescopedmethod we just added. Then it'll work just like before with the ID token and session cookie conflict.Backwards compatibility
Upgrading to this patched version will result in the previous cache key being ignored, and the appropriate keys to be fetched in the new scoped stores on first use. The previous cache key will stay in the KV until its TTL, then it'll be garbage collected without any manual cleanup necessary.
Notes
In order to run the test suite with Node 26 and pnpm 11, I've had to add the following patch (not committed here):
All tests are green.
This PR was AI-assisted but I've carefully steered and reviewed the code, including the new tests. PR description written by hand.
Happy to revisit with a different approach if you think there's a better way to solve this.
Also no rush for merging this, just gonna use my fork in the meantime for my own projects.