Repository navigation
Post the flow's SSO sessions on the authorization callback - #147
Conversation
A sign-in or sign-up that completes an authorization request posts its assertion to /oauth2/auth/callback. The flow's responses also return the SSO sessions its steps start, and the server keeps them with the code when the application asked for the device_sso scope, so the code exchange can return them as a device secret. Collect the sessions from every response of the authorization request, in memory only, drop the ones a later step ends, and post them with the assertion. A failed sign-in posts none. Refs thunder-id/thunderid#6144
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
🔀 Cross-SDK feature parity, no ports neededThis pull request is labelled |
| // The SSO sessions each authorization request's flow responses returned, held in memory until the | ||
| // callback posts them. A session can start on any step, not only the last, so every response is read. | ||
| // They are never persisted, so a reload in the middle of the flow drops them. | ||
| const collectedSessions = new Map<string, Map<string, EmbeddedFlowSessionData>>(); |
There was a problem hiding this comment.
@coderabbitai Check whether this can cause any issues when the code is run on a SSR app.
There was a problem hiding this comment.
🧩 Analysis chain
-
🏁 Script executed:
#!/bin/bash fd -H '^(AGENTS\.md|flowSessions\.ts)$' . --exclude node_modules --exclude .git ast-grep outline packages/javascript/src/api/auth/flowSessions.ts --items all rg -n -C 4 'flowSessions|collectedSessions' packages
Length of output: 5677
🏁 Script executed:
#!/bin/bash cat AGENTS.md ast-grep outline packages/javascript/src/api/auth/executeEmbeddedSignInFlow.ts --items all ast-grep outline packages/javascript/src/api/auth/executeEmbeddedSignUpFlow.ts --items all nl -ba packages/javascript/src/api/auth/flowSessions.ts nl -ba packages/javascript/src/api/auth/executeEmbeddedSignInFlow.ts | sed -n '1,260p' nl -ba packages/javascript/src/api/auth/executeEmbeddedSignUpFlow.ts | sed -n '1,270p' rg -n -C 3 '\bexecuteEmbeddedSign(In|Up)Flow\s*\(|\binterface EmbeddedFlowSessionData\b|authId:' packages --glob '*.{ts,tsx}' | head -240
Length of output: 54630
🏁 Script executed:
#!/bin/bash ast-grep outline packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts --items all nl -ba packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts | sed -n '75,155p' rg -n -C 3 'executeEmbeddedSignInFlow|executeEmbeddedSignUpFlow' packages/javascript/src --glob '*.ts' --glob '!**/__tests__/**' rg -n -C 3 'EmbeddedFlowSessionData|authId\??:' packages/javascript/src/models/embedded-flow* rg -n -C 3 'executeEmbeddedSignInFlow|executeEmbeddedSignUpFlow' packages/node packages/nextjs packages/nuxt --glob '*.{ts,tsx}' --glob '!**/__tests__/**'
Length of output: 18676
@brionmario Yes—if the embedded flow runs on the server with authId, this module-level Map can cause issues. Importing the module during SSR does not itself cause a browser-API error.
I checked the collection logic and both flow executors.
| Risk | Finding |
|---|---|
| Shared server state | In flowSessions.ts, Line 17, the Map belongs to the loaded module, not an HTTP request or SDK client. All requests in that server instance share it. Distinct authId values separate entries, but the key has no server or tenant namespace. This alone does not prove cross-user leakage. |
| Unbounded credential retention | Cleanup depends on takeFlowSessions(). Abandoned flows and some error paths leave session handles in memory without expiry or a size limit. A browser reload does not clear the server’s map, despite the comment at Line 16. |
| Missing sessions across instances | If later flow steps reach another worker or serverless instance, that instance cannot retrieve earlier sessions. The callback can then omit sessions collected on earlier steps. |
| No SSR-to-browser transfer | Server memory and browser memory are separate. If a flow starts during SSR and continues after hydration, the browser cannot access the server’s collected sessions. |
There is also a retry issue that is not specific to SSR: both executors call takeFlowSessions() before the callback succeeds. If the callback fails, the collected sessions have already been deleted.
Current Nuxt behavior: the inspected server-side signIn() call in packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts, Line 121, does not pass authId. That path does not populate this map. The exported core APIs still permit server callers to pass authId.
Recommendation: make the collection explicit and scoped to the authorization flow. If flows span server requests or instances, use a protected server-side store with tenant/server namespacing, expiry, and terminal cleanup. A request-local map alone will not preserve a multi-request flow. If collection is intended only for browser execution, enforce that boundary explicitly.
This assessment is based on source inspection. I did not run runtime tests.
You are interacting with an AI system.
Moves @thunderid/react to 1.11.1 and @thunderid/react-router to 1.10.1 (@thunderid/browser 1.9.1 and @thunderid/javascript 1.10.1 follow transitively). These releases post the SSO sessions a flow's responses return on the authorization callback (thunder-id/javascript-sdks#147), so a sign-in through the Gate that asks for the device_sso scope now receives a device secret (thunder-id#6227). They also bring Device Grant support (thunder-id/javascript-sdks#140), an ouId prop on GroupMembers (thunder-id/javascript-sdks#146), and granted permissions read from the backend session in backend mode (thunder-id/javascript-sdks#148). Refs thunder-id#6144 Signed-off-by: ImalshaD <plid475@gmail.com>
Purpose
A sign-in or sign-up that completes an authorization request posts its assertion to
/oauth2/auth/callback; this is how the ThunderID Gate finishes a browser sign-in. The flow's responses also return the SSO sessions its steps start, insessionData. With thunder-id/thunderid#6227, the server keeps the sessions posted on the callback with the code when the application asked for thedevice_ssoscope, and the code exchange returns them as adevice_secretthat another application can exchange to sign the user in without a redirect. Until the SDK posts them, a sign-in through the Gate receives no device secret.Approach
api/auth/flowSessions.tskeeps, perauthId, the sessions every flow response returned,INCOMPLETEones included, since a session can start on any step. A session a later step lists inclearedSessionsis dropped. They are held in memory only and never persisted or logged, so a reload in the middle of the flow drops them.executeEmbeddedSignInFlowposts the collected sessions with the assertion on a completed sign-in. A failed sign-in, which posts an error assertion and issues no code, discards them.executeEmbeddedSignUpFlowdoes the same, since a registration flow that signs the user in also completes the authorization request through the callback.sessionDataandclearedSessionsare added toEmbeddedSignInFlowResponseandEmbeddedSignUpFlowResponse, andEmbeddedFlowSessionDatais exported.Not in this PR: keeping the
device_secreta code exchange returns, and exchanging it from the SDK. Those follow separately.Related Issues
Related PRs
SDK parity
/oauth2/auth/callback./oauth2/auth/callback./oauth2/auth/callback.The SDK development specification does not describe the authorization callback path yet, so an update to
spec.mdandthreat-model.mdin thunder-id/thunderid follows.Checklist
executeEmbeddedSignInFlow.test.ts,executeEmbeddedSignUpFlow.test.ts)breaking changelabel added.parity/prs-raisedorparity/prs-not-neededadded.parity/prs-raised, the port links are posted as a reply on the parity check's comment.Security checks
A session handle resumes the session without authentication, so the collected sessions are held only in memory, for the authorization request they belong to, and are sent only to the server's own callback.
🤖 Generated with Claude Code