feat(server,web): add SSO logout tracking with response header#2142
feat(server,web): add SSO logout tracking with response header#2142soneda-yuya wants to merge 11 commits intomainfrom
Conversation
…ut tracking Incorporate reearth/reearth-accounts#212 which adds latest_logout_at field, logout mutation, and X-Latest-Logout-At response header for cross-service SSO logout detection. Adapt to breaking API changes in workspace client types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add logout mutation, Me.latestLogoutAt field, and X-Latest-Logout-At response header middleware to enable cross-service SSO logout detection by proxying to reearth-accounts API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compare JWT iat with X-Latest-Logout-At response header timestamp. When a newer logout is detected, re-fetch access token with cacheMode: "off" to ensure the session reflects the latest auth state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Send logout GraphQL mutation to record latestLogoutAt timestamp in accounts API before clearing the Auth0 SSO session. This enables other services to detect the logout via X-Latest-Logout-At header. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds server-side logout timestamp tracking and propagates it to the web client to detect SSO logout across sessions, refreshing tokens when the server indicates a newer logout.
Changes:
- Server: add
logoutGraphQL mutation proxy + exposeMe.latestLogoutAtand returnX-Latest-Logout-Atresponse header (with CORS exposure). - Web: capture
X-Latest-Logout-Atfrom GraphQL responses, compare against JWTiat, and force-refresh access tokens when server logout is newer. - Web: best-effort
logoutmutation call before invoking the Auth0 SDK logout.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/services/gql/provider/links/uploadLink.ts | Captures X-Latest-Logout-At from GraphQL responses and updates in-memory logout timestamp. |
| web/src/services/auth/logoutTimestamp.ts | Adds in-memory storage/getter/updater for latest logout timestamp. |
| web/src/services/auth/auth0Auth.ts | Adds JWT iat extraction + conditional token refresh, and sends logout mutation before Auth0 logout. |
| server/internal/app/auth_client.go | Maps LatestLogoutAt from accounts user model into domain user builder. |
| server/internal/app/app.go | Exposes X-Latest-Logout-At via CORS and adds middleware to attach header to private API responses. |
| server/internal/adapter/gql/resolver_mutation_workspace.go | Adapts workspace client input types to accounts API breaking changes. |
| server/internal/adapter/gql/resolver_mutation_user.go | Adds logout mutation and maps LatestLogoutAt from accounts user model. |
| server/internal/adapter/gql/gqlmodel/models_gen.go | Adds latestLogoutAt field to Me GraphQL model. |
| server/internal/adapter/gql/gqlmodel/convert_user.go | Populates Me.latestLogoutAt when available. |
| server/internal/adapter/gql/generated.go | Regenerates gqlgen outputs for new schema fields/mutation. |
| server/gql/user.graphql | Adds Me.latestLogoutAt and Mutation.logout. |
| server/go.mod / server/go.sum | Bumps reearth-accounts dependency to version including logout tracking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
🚀 Cloud Run Preview Deployed |
Replace direct fetch call in auth0Auth.ts with Apollo Client mutation via useMeMutations. Each logout call site (Dashboard, Navbar, GlobalModal) now calls logoutFromAccount() before auth.logout(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace module-level let variable with Jotai atom to align with existing state management patterns. The GqlProvider passes the setter to uploadLink as a callback, and auth0Auth reads via useLatestLogoutAt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address Copilot review feedback: - Fix JWT iat parsing to handle base64url encoding (- / _ and padding) - Extract JWT utils to jwtUtils.ts for testability - Add unit tests for base64UrlDecode and getJwtIat - Add server test for X-Latest-Logout-At header middleware Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing useLatestLogoutAt to @reearth/services/state mock in test utils to fix CI test failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Rename auth0Auth.test.ts to jwtUtils.test.ts to match tested module - Add comment clarifying latestLogoutAt uses Unix seconds (matches JWT iat) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
What I've done
Server
reearth-accountsdependency to include logout tracking (feat(server): add server-side logout tracking for cross-service SSO reearth-accounts#212)logoutGraphQL mutation proxied to accounts APIMe.latestLogoutAtfield to GraphQL schemaX-Latest-Logout-Atresponse header middlewareExposeHeadersforX-Latest-Logout-AtWeb
X-Latest-Logout-Atheader from API responses and store in Jotai atomlatestLogoutAtwith JWTiatingetAccessToken— if logout is newer, re-fetch token withcacheMode: "off"to bypass cache. In the same browser, this causes the re-fetch to fail (SSO session is already gone), resulting in the user being redirected to login.logoutmutation before Auth0 SDKlogout()to record timestamp in accounts APIWhat I haven't done
How I tested
go build ./...andgo test ./internal/adapter/gql/... ./internal/app/...passyarn typeandyarn buildpassWhich point I want you to review particularly
X-Latest-Logout-Atheaderiatcomparison →cacheMode: "off"re-fetch → logoutMemo
Checklist
🤖 Generated with Claude Code