auth: SSO bridge — gateway-side finish + signout + navbar deep-link to canonical (branch 6b)#15
Merged
Merged
Conversation
…ink to canonical (branch 6b)
Companion to sof-ai-repo PR #55. ai.thevrschool.org is the canonical
auth surface; sof.ai consumes a short-lived signed bridge token to
mint its own NextAuth session cookie when the visitor returns.
Routes:
GET /api/auth/sso/finish?token=&next=
1. Verify bridge token (signature, expiry, audience = sof.ai)
2. Mint NextAuth-compatible JWT via next-auth/jwt encode()
3. Set __Secure-next-auth.session-token cookie on this domain
4. Best-effort touchUserProfile so the shared row exists
5. 302 to next (relative path on this gateway)
GET /api/auth/sso/signout?next=
Sign-out fan-out target. GET-driven (NextAuth's built-in is POST
+ CSRF, awkward for cross-TLD redirect chains). Clears local
cookie, redirects to next (allow-listed sister-site hosts only).
UX:
- Navbar 'Sign in' deep-links to ai.thevrschool.org SSO handoff
with the current path as next, so signed-in visitors round-trip
invisibly and signed-out visitors see the canonical /signin.
- Sign out fans out: sof.ai signout -> canonical signout -> home.
One click clears all three sites (cookie on sof.ai + the
.thevrschool.org cookie covering ai. + apex).
Helpers:
- lib/sso/bridgeToken.ts: HMAC-SHA256 verifier (mirror of canonical
minter; same custom JWT-like envelope; zero new deps)
- lib/sso/canonical.ts: build-handoff + sign-out-chain helpers used
by the navbar so the routes aren't string-concatenated inline
- lib/users.ts: best-effort /users/touch helper (also being added
in PR #12; coexists)
Local /signin still works as a fallback (guest mode is host-local
and cannot be bridged, so it stays here). Build + lint green; no new
deps.
Co-Authored-By: Dr. Freedom Cheteni <freedom@thevrschool.org>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Mirror of sof-ai-repo PR #55 follow-up. resolveNext accepted relative paths without sanitising CR/LF/control chars; without this, a crafted ?next=/%0d%0aSet-Cookie:+evil=1 either crashes Node's HTTP layer or smuggles headers into the redirect response. Same fix applied here. Co-Authored-By: Dr. Freedom Cheteni <freedom@thevrschool.org>
…out resolveNext Mirrors sof-ai-repo PR #55 follow-up. WHATWG URL parser normalises backslashes to forward slashes for http/https, so '/\\evil.com' resolves to 'https://evil.com/' — open redirect via Location header. Co-Authored-By: Dr. Freedom Cheteni <freedom@thevrschool.org>
…sh safeRelativeNext Same backslash bypass as the signout route. WHATWG URL parser normalises backslashes to forward slashes for http/https, so '/\\evil.com' resolves to 'https://evil.com/'. Without this guard on the finish route's next param, an attacker could craft '?token=VALID&next=/%5Cevil.com' to redirect users to a phishing page right after SSO completes. Co-Authored-By: Dr. Freedom Cheteni <freedom@thevrschool.org>
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.
Summary
Companion to sof-ai-repo#55. Makes
ai.thevrschool.orgthe canonical auth surface for the School of Freedom ecosystem.sof.aidefers all sign-in to it and consumes a short-lived signed bridge token to mint its own local NextAuth cookie.Browsers won't share cookies across
sof.ai↔*.thevrschool.org(different TLDs), so cookie-domain SSO isn't an option. The bridge is the standard fix.New routes (gateway side)
GET /api/auth/sso/finish?token=&next=— verifies the signed bridge token (HMAC-SHA256 over the payload using sharedNEXTAUTH_SECRET, audience-bound tosof.ai, 60s expiry), mints a NextAuth-compatible JWT vianext-auth/jwt'sencode(), sets__Secure-next-auth.session-tokenonsof.ai, best-effort upserts the sharedUserProfilerow, then 302s tonext. Tampered/expired tokens redirect to/signin?error=BridgeTokenInvalidso timing attacks can't differentiate failure modes.GET /api/auth/sso/signout?next=— sign-out fan-out target. GET-driven (NextAuth's built-in/api/auth/signoutis POST + CSRF, awkward for cross-TLD redirect chains). Clears the local cookie +.0/.1/.2chunked variants, then 302s tonext(allow-listed to sister-site hosts only).UX wiring
https://ai.thevrschool.org/api/auth/sso/handoff?domain=sof.ai&next=<current-path>. Two behaviours:/signinappears, then bounces back automaticallysof.ai/api/auth/sso/signout→ai.thevrschool.org/api/auth/sso/signout→https://sof.ai/. One click clears the cookie onsof.aiAND the.thevrschool.orgcookie coveringai.+ the apexwww.in one round-trip./signinstill works as a fallback. Guest mode is intentionally host-local — the canonical handoff refuses*@guest.sof.aisince their identity is meant to be ephemeral and not extend across TLDs.Helpers
lib/sso/bridgeToken.ts— HMAC-SHA256 verifier; mirror of the canonical minter; same custom JWT-like envelope (b64url payload + signature); zero new deps (node:cryptoonly).lib/sso/canonical.ts—buildHandoffUrl(next)+buildSignOutChain(next)so the navbar doesn't string-concatenate URLs inline. ReadsNEXT_PUBLIC_CANONICAL_AUTH_URLfor staging overrides; defaults tohttps://ai.thevrschool.org.lib/users.ts— best-effort/users/touchhelper. Also added by PR #12 with identical content; the two PRs will merge cleanly together.Review & Testing Checklist for Human
NEXTAUTH_SECRETon thesofaiVercel project (production) is byte-identical to the value onsof-ai(the AI School). The bridge token's HMAC won't verify if they diverge — every sign-in click would loop forever between the two sites.sof.aiwhile signed out everywhere → expect to land onai.thevrschool.org/signin. Sign in via magic-link there → expect to bounce back tosof.aiautomatically with the avatar visible.sof.ai, click "Sign out" → expect the cookie cleared on bothsof.aiandai.thevrschool.org(verify by reloading both sites). The chain redirect is brief but visible in the address bar./signin(fallback path; ephemeral identity is not bridged).Notes
lib/users.tsis identical to the file added by PR auth: signIn callback upserts shared UserProfile (branch 3c) #12. If PR auth: signIn callback upserts shared UserProfile (branch 3c) #12 merges first, this PR'sgit diffwill show the file as already-present and merge cleanly. Same content, same function name (touchUserProfile).algfield) — we don't need format flexibility, this keeps the dependency footprint to zero. Both sides ship from the same workspace pattern so the format is fixed.Link to Devin session: https://app.devin.ai/sessions/091fc09f81b5489292102ebcdc173dbb
Requested by: @DearMrFree