Skip to content

auth: SSO bridge — gateway-side finish + signout + navbar deep-link to canonical (branch 6b)#15

Merged
DearMrFree merged 5 commits into
mainfrom
devin/1777506082-sso-bridge-finish
Apr 30, 2026
Merged

auth: SSO bridge — gateway-side finish + signout + navbar deep-link to canonical (branch 6b)#15
DearMrFree merged 5 commits into
mainfrom
devin/1777506082-sso-bridge-finish

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 29, 2026

Summary

Companion to sof-ai-repo#55. Makes ai.thevrschool.org the canonical auth surface for the School of Freedom ecosystem. sof.ai defers 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 shared NEXTAUTH_SECRET, audience-bound to sof.ai, 60s expiry), mints a NextAuth-compatible JWT via next-auth/jwt's encode(), sets __Secure-next-auth.session-token on sof.ai, best-effort upserts the shared UserProfile row, then 302s to next. Tampered/expired tokens redirect to /signin?error=BridgeTokenInvalid so 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/signout is POST + CSRF, awkward for cross-TLD redirect chains). Clears the local cookie + .0/.1/.2 chunked variants, then 302s to next (allow-listed to sister-site hosts only).

UX wiring

  • Navbar "Sign in" now deep-links to https://ai.thevrschool.org/api/auth/sso/handoff?domain=sof.ai&next=<current-path>. Two behaviours:
    • Already signed in there → instant return with cookie set, invisible to the user
    • Not signed in there → AI School's /signin appears, then bounces back automatically
  • Sign out fans out: sof.ai/api/auth/sso/signoutai.thevrschool.org/api/auth/sso/signouthttps://sof.ai/. One click clears the cookie on sof.ai AND the .thevrschool.org cookie covering ai. + the apex www. in one round-trip.
  • Local /signin still works as a fallback. Guest mode is intentionally host-local — the canonical handoff refuses *@guest.sof.ai since 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:crypto only).
  • lib/sso/canonical.tsbuildHandoffUrl(next) + buildSignOutChain(next) so the navbar doesn't string-concatenate URLs inline. Reads NEXT_PUBLIC_CANONICAL_AUTH_URL for staging overrides; defaults to https://ai.thevrschool.org.
  • lib/users.ts — best-effort /users/touch helper. Also added by PR #12 with identical content; the two PRs will merge cleanly together.

Review & Testing Checklist for Human

  • Confirm NEXTAUTH_SECRET on the sofai Vercel project (production) is byte-identical to the value on sof-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.
  • After sof-ai-repo PR #55 + this PR are deployed, click "Sign in" on sof.ai while signed out everywhere → expect to land on ai.thevrschool.org/signin. Sign in via magic-link there → expect to bounce back to sof.ai automatically with the avatar visible.
  • While signed in on sof.ai, click "Sign out" → expect the cookie cleared on both sof.ai and ai.thevrschool.org (verify by reloading both sites). The chain redirect is brief but visible in the address bar.
  • Verify guest sign-in still works on local /signin (fallback path; ephemeral identity is not bridged).

Notes

Link to Devin session: https://app.devin.ai/sessions/091fc09f81b5489292102ebcdc173dbb
Requested by: @DearMrFree


Open in Devin Review

…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>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sofai Ready Ready Preview, Comment Apr 30, 2026 0:18am

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>
Copy link
Copy Markdown
Contributor Author

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

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

…t allow-list

Mirrors sof-ai-repo PR #55 follow-up. Same trusted-host bypass via
javascript://sof.ai existed 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>
devin-ai-integration[bot]

This comment was marked as resolved.

…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>
@DearMrFree DearMrFree merged commit 7a2ffd9 into main Apr 30, 2026
3 checks passed
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.

1 participant