Skip to content

fix(server-sdk): read and write the project-scoped auth cookies - #2277

Merged
albertoelias-crossmint merged 7 commits into
mainfrom
albertoe/server-auth-scoped-cookies
Nov 5, 2026
Merged

albertoelias-crossmint merged 7 commits into
mainfrom
albertoe/server-auth-scoped-cookies

Conversation

@albertoelias-crossmint

@albertoelias-crossmint albertoelias-crossmint commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

fix(server-sdk): read and write project-scoped auth cookies

Problem

A developer signs a user in with @crossmint/client-sdk-auth (1.3.4 or later) and calls crossmintAuth.getSession(request) in their server code. It throws Failed to get session or No auth material found, even though the user is signed in.

The client now stores the session in cookies named after the project: crossmint-jwt-<projectId> and crossmint-refresh-token-<projectId>. The server SDK still looks for crossmint-jwt and crossmint-refresh-token, so it never finds them. The other direction breaks too: a custom refresh route that stores its cookies through the server SDK writes names the client never reads, so the refreshed session is lost.

Today the only way out is to read the scoped cookies yourself and pass { jwt, refreshToken } to getSession, which the README does not tell you to do. In Next.js Server Components there is no request object, so you also have to know the scoped names.

What this PR does

  • getSession(request), handleCustomRefresh and logout read the project-scoped cookies. The legacy bare names are used only when neither scoped cookie is present, and the two sets are never mixed (a legacy JWT is never paired with a scoped refresh token).
  • storeAuthMaterial writes the scoped names and expires the legacy ones.
  • New getAuthCookieNames() returns the scoped names, for code with no request object.
  • getScopedCookieName moves to @crossmint/common-sdk-auth; the client uses it, so client and server cannot drift.
  • The server README and the nextjs-ssr example use getSession(request) and getAuthCookieNames().
flowchart LR
  subgraph Before
    C1[client writes crossmint-jwt-PROJECT] -.never read.-> S1[server reads crossmint-jwt]
    S1 --> E1[No auth material found]
  end
  subgraph After
    C2[client writes crossmint-jwt-PROJECT] --> S2[server reads scoped name, legacy only if no scoped cookie]
    S2 --> OK[session]
  end
Loading

Result

Before After
getSession(request) with client 1.3.4+ cookies throws Failed to get session returns the session
Custom refresh route cookies bare names, ignored by the client scoped names, legacy names expired
Next.js Server Component developer hard-codes bare names crossmintAuth.getAuthCookieNames()
Lines a developer writes read two cookies by hand, pass both getSession(request) (one line)

Unchanged: the getSession({ jwt, refreshToken }) form, JWT audience checks, cookie options, the legacy-only case (still readable).

What to check when reviewing

  • packages/server/src/auth/utils/cookies.ts: source selection. If either scoped cookie is present both values come from the scoped names, otherwise from the legacy names. An empty scoped cookie counts as absent.
  • packages/server/src/auth/CrossmintAuthServer.ts: projectId is passed to every cookie read and write.
  • apps/auth/nextjs-ssr: the middleware keeps response.cookies.set so Next.js hands refreshed cookies to Server Components in the same request.
  • Behaviour change: writing a session now also emits expiry Set-Cookie headers for the legacy names.
  • Version skew: client SDKs from before fix: scope JWT cookies by project ID to prevent audience mismatch warnings #1546 (April 2026) read only the bare names. An app that upgrades this package but keeps such a client will see the client lose its session after the first server-side refresh. Apps on a current client are unaffected.
  • Overlap: fix(server-sdk): throw on failed auth requests instead of returning the error body #2278 edits CrossmintAuthServer.ts, its test and packages/server/README.md; whichever merges second takes the other's change (the changes are in different methods and README sections). The open quorum PRs touch wallets code, not these files.

Test plan

  • pnpm --filter @crossmint/server-sdk test:vitest: 75 passed. @crossmint/client-sdk-auth: 32 passed.
  • Builds: common-sdk-auth, client-sdk-auth, server-sdk; tsc --noEmit on apps/auth/nextjs-ssr.
  • The new tests fail on the old code: reverting CrossmintAuthServer.ts fails the getSession and logout scoped-cookie tests; reverting cookies.ts fails 10 of 15 cookie tests.
  • Manual: sign in with the nextjs-ssr example and reload / and /direct.

Checklist

  • Changeset: server-sdk, common-sdk-auth, client-sdk-auth (patch)
  • README updated; the snippet matches the example app
  • Biome clean on changed files (one existing warning in storage.ts, not from this PR)
  • No Co-Authored-By lines

Rollback

Revert the PR. Cookies written while it was live use the scoped names, which the client already reads; the legacy expiry headers only remove old bare-named cookies.

Closes WAL-13393

🤖 Generated with Claude Code


Devin Review

client-sdk-auth 1.3.4+ stores the session as crossmint-jwt-<projectId> and
crossmint-refresh-token-<projectId>, but the server SDK only read and wrote the
bare names, so getSession(request) saw no session and a custom refresh route
stored cookies the client never read.

- getSession(request), handleCustomRefresh and logout read the scoped names
  first and fall back to the legacy bare names, per cookie.
- storeAuthMaterial writes the scoped names and expires the legacy ones.
- New CrossmintAuth.getAuthCookieNames() for code with no request object
  (Next.js Server Components).
- getScopedCookieName moves to common-sdk-auth; the client uses it.
- README (source of the docs.crossmint.com server SDK page) and the nextjs-ssr
  example use getSession(request) / getAuthCookieNames() instead of bare names.
@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 52ba42b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@crossmint/server-sdk Patch
@crossmint/common-sdk-auth Patch
@crossmint/client-sdk-auth Patch
@crossmint/auth-ssr-nextjs-demo Patch
crossmint-auth-node Patch
@crossmint/client-sdk-react-base Patch
@crossmint/client-sdk-react-native-ui Patch
@crossmint/client-sdk-react-ui Patch
@crossmint/wallets-sdk Patch
@crossmint/wallets-playground-expo Patch
@crossmint/client-sdk-nextjs-starter Patch
@crossmint/wallets-quickstart-devkit Patch
@crossmint/wallets-playground-react Patch
@crossmint/integration-tests Patch

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

devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps

greptile-apps Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new blocking issue was found.

Reviews (2) · Last reviewed commit: "fix(server-sdk): expire legacy auth cook..." · Reviewed by Greptile

greptile-apps[bot]

This comment was marked as resolved.

…er-key errors inside the example's error handling
@albertoelias-crossmint

Copy link
Copy Markdown
Collaborator Author

@greptileai review

… a CrossmintAuth method

Callers with no request object read the project ID from the API key with the re-exported validateAPIKey.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment thread apps/auth/nextjs-ssr/src/middleware.ts Resolved
@albertoelias-crossmint
albertoelias-crossmint merged commit 0c053ed into main Nov 5, 2026
8 checks passed
@albertoelias-crossmint
albertoelias-crossmint deleted the albertoe/server-auth-scoped-cookies branch November 5, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants