Repository navigation
Move to stateful session auth - #757
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
3f66a43 to
850b78d
Compare
| const token = await new SignJWT() | ||
| const token = await new SignJWT({ sid: opts.sessionId }) | ||
| .setProtectedHeader({ alg: ALG, kid: opts.kid, typ: "JWT" }) | ||
| .setSubject(opts.subject) |
There was a problem hiding this comment.
Can we consider removing the sub claim here?
Since we're going to use sessions, people should always convert the session ID to a user ID through a DB query. Including a sub field incentivizes people to use the user ID directly, which would fail open on revocation
If developers want to authenticate to another system, they should take the responsibility of issuing their own JWTs (potentially through the new syscall)
There was a problem hiding this comment.
After live and chat discussion, the session ID is now getting stored in the sub.
| const nowSeconds = Math.floor(Date.now() / 1000); | ||
| const expSeconds = nowSeconds + opts.expiresInSeconds; | ||
| const token = await new SignJWT() | ||
| const token = await new SignJWT({ sid: opts.sessionId }) |
There was a problem hiding this comment.
sid seems to be a field defined by the OIDC spec, not by the “pure” JWT spec. But it seems fine to use it still, even though we aren’t implementing OIDC.
I considered the alternatives and nothing looks better. Using sub to store the session ID sounds more misleading and potentially spec-breaking if we wanted to implement OIDC later. And using a custom claim name isn’t really helpful either.
There was a problem hiding this comment.
If we implement OIDC later, that would be more of an outward facing system and we'd issue different tokens.
850b78d to
78fe425
Compare
56367bb to
1ac66fc
Compare
78fe425 to
dfc4870
Compare
sid claim in the JWT|
Thanks for the initial review, @Nicolapps ! Requesting another pass since this has changed in structure based on discussions yesterday. |
1ac66fc to
47f463e
Compare
dfc4870 to
a8fadc6
Compare
commit: |
a8fadc6 to
c21af2b
Compare
c21af2b to
9f51b12
Compare
47f463e to
0fdc1ba
Compare
9f51b12 to
a70f14d
Compare
a70f14d to
8740231
Compare
8740231 to
1c5ba2c
Compare
| * A session is live exactly as long as its row exists: sign-out, refresh-token | ||
| * reuse detection, and expired-token cleanup all delete the row. | ||
| * | ||
| * This is a component-internal function (callable by the app, not by end-user |
There was a problem hiding this comment.
I think that “component-internal” here is misleading, it sounds like it’s an internal function defined in the component, or that it’s not meant to be called by the app. We probably need a better term (maybe something like “This is a function exposed by the component to the app, it’s not exposed to the end user”?)
There was a problem hiding this comment.
Thanks, touched up the comment (and a similar one above it).
1c5ba2c to
f77ca58
Compare
The access token's `sub` claim is now the session id, not the user id. The user is whoever the session belongs to, for as long as the session exists, so a revoked session stops resolving to a user before its access token expires. - The core component gains `getSession`, which returns the `userId` of a live session, or `null` when the row is gone (or the string is not a session id). - `getAuthUserId` from `@convex-dev/auth/server` looks the user up through that session. It still takes just `ctx`, reaching the core component under its default mount name, `auth`; an app that mounts it under another name passes the component as a second argument. - `AuthCore` carries its `component`, so the sign-in methods pass it to `getAuthUserId` and keep working under a renamed mount; the passkey management functions get it through `UsernamePasskeyConfig`. - `@convex-dev/auth/test` gains `sessionIdentity(tokens)`, the identity for `t.withIdentity` that Convex derives from a sign-in's access token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f77ca58 to
242704a
Compare

Prior to this change, a non-expired JWT was all that was needed to run functions as a user. Now there needs to be a live session associated with the JWT.
To make this happen, the JWT now carries the session ID in the
subclaim, not the user ID. In order for functions to access the user ID, it has to be looked up from a live session.The
getAuthUserIdfunction now takes an optional reference to the coreauthcomponent. It works out of the box by default, but allows passing a custom reference if an app renames it.Most of the other changes are to examples and tests.