Repository navigation
feat(server-sdk): return the user from getSession on refresh, refresh the JWKS in the background - #2290
Merged
Conversation
A server that needs the user's email called getUser after getSession: one more round trip per request. When getSession has to refresh the session, the refresh response already carries the user (and verifyRefreshedAuthMaterial already checks it is the JWT's subject), so return it as `session.user`. With a still-valid JWT there is no user to return: the JWT only carries sub/aud/iss/exp. Adding email and phone_number claims to the JWT would cover that case too; that is a backend change, proposed separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The remote key set expired after 10 minutes, and the next verification then waited for a new download (a separate TLS connection, about 1 s from a cold host). Keys now never expire inside jose; a wrapper re-downloads them in the background once they are 10 minutes old and keeps verifying with the current ones meanwhile. While the endpoint fails it retries at most every 30 s and stops trusting the keys after an hour. Unknown key ids still trigger jose's own blocking download, so rotation works as before. Tests now run verifyCrossmintJwt against a real JWKS endpoint on localhost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/server/src/auth/CrossmintAuthServer.ts
🦋 Changeset detectedLatest commit: 5ccec04 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
[Critical impact] The PR appears safe to merge; no blocking issues were found. Reviews (2) · Last reviewed commit: "fix(server-sdk): count JWKS age from eve..." · Reviewed by Greptile |
jose re-downloads the key set by itself when a token names an unknown key id, but the wrapper only timed its own downloads. An hour after its last one, verification then failed during an endpoint outage although the keys were minutes old. Read the age from jose's jwksCache stamp instead, which jose updates after every successful download. Tests: the age after a download triggered by a new key; a completed background refresh replaces the keys without a second download. The console.error spy is restored after each test.
Collaborator
Author
|
@greptileai review |
guilleasz-crossmint
approved these changes
Nov 6, 2026
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.
feat(server-sdk): return the user from getSession on refresh, refresh the JWKS in the background
Problem
An app that needs the user on the server calls
getSessionand thengetUseron each request. That is one more round trip (0.35–1.5 s from a cold host to staging) just to get the email, because the Crossmint Auth JWT carries onlysub,aud,issandexp.When
getSessionrefreshes, the refresh response already contains that user. The SDK dropped it.Separately, the JWKS cache expired every 10 minutes. The next verification then waited for a new download, about 1 s from a cold host, because jose fetches over its own connection.
This is a correction to what we thought: a new
CrossmintAuth.from()does not re-download the keys. They have been cached per URI across instances since #912; a new instance verifies in 8 ms.What this PR does
getSessionreturnsuser(email, phone number, linked accounts) when it refreshed the session.verifyRefreshedAuthMaterialalready checks that this user is the JWT'ssub.AuthSessionin common-sdk-auth gains the optionaluser. When the JWT was still valid,userstays undefined. We decided not to add identity claims to the JWT, sogetUseris still needed in that case.cacheMaxAge: Infinity). A wrapper injwksClient.tstracks their age instead:JwksUnavailableErrorif the download fails.kidstill goes through jose's own blocking download, so key rotation behaves as before.What to check when reviewing
jwks-client.test.tsno longer mocks jose: it runsverifyCrossmintJwtagainst a real JWKS server on localhost and fakes onlyDate. The old tests asserted the arguments passed tocreateRemoteJWKSet.getSessiondoc comment).crossmint-auth-server.test.tsin different tests.Test plan
pnpm --filter @crossmint/server-sdk test:vitest: 79 passed, after merging main.userpass-through.kid: a blocking download.tsc --noEmitandbiome checkon the server package are clean.🤖 Generated with Claude Code