From d794a3b6ea88715896a1aba8618d2315ca36aa3d Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Mon, 23 Feb 2026 14:25:07 -0600 Subject: [PATCH] Signed-off-by: OpenClaw Bot --- docs/sso-implementation-plan.md | 279 ++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 docs/sso-implementation-plan.md diff --git a/docs/sso-implementation-plan.md b/docs/sso-implementation-plan.md new file mode 100644 index 0000000..02646d5 --- /dev/null +++ b/docs/sso-implementation-plan.md @@ -0,0 +1,279 @@ +# Shared SSO Implementation Plan (Mission Control + Related Apps) + +## Summary +Implement single sign-on across apps by using one parent domain and one shared session cookie, with Supabase Auth as the identity source and a central auth origin (`auth.topdoglabs.com`). +Use one dedicated auth project/service for this logic so auth code is not duplicated across app projects. + +This gives: +- One login across apps +- Silent cross-app authentication +- Global logout + +--- + +## Chosen Decisions +1. Use custom subdomains under one parent domain. +2. Keep Supabase as the central identity provider. +3. Use silent auto-login between apps. +4. Use global logout across apps. + +--- + +## Target Domain Layout +- `auth.topdoglabs.com` -> central auth service +- `mission.topdoglabs.com` -> Mission Control +- `gantt.topdoglabs.com` -> Gantt Board +- (optional) `blog.topdoglabs.com` -> Blog Backup + +Important: shared-cookie SSO will not work across `*.vercel.app` app URLs. + +--- + +## Auth and Session Architecture +1. Centralize login/logout/session endpoints on `auth.topdoglabs.com`. +2. Keep credentials and user identity in Supabase Auth. +3. Use a shared `sessions` table in Supabase for web sessions. +4. Issue one cookie for all subdomains: + - Name: `tdl_sso_session` + - `Domain=.topdoglabs.com` + - `Path=/` + - `HttpOnly` + - `Secure` (production) + - `SameSite=Lax` +5. Each app middleware: + - reads `tdl_sso_session` + - validates via auth/session endpoint + - redirects to auth login if missing/invalid, with `return_to` URL +6. Logout from any app: + - revoke server session + - clear shared cookie for `.topdoglabs.com` + - signed out everywhere + +--- + +## API Contracts +### `POST /api/sso/login` (auth domain) +Input: +```json +{ "email": "user@example.com", "password": "********", "rememberMe": true } +``` +Behavior: +- authenticates against Supabase Auth +- creates shared session row +- sets shared cookie + +Response: +```json +{ "success": true, "user": { "...": "..." }, "session": { "expiresAt": "ISO8601", "rememberMe": true } } +``` + +### `GET /api/sso/session` (auth domain) +Behavior: +- reads `tdl_sso_session` +- validates against sessions table + +Response: +```json +{ "authenticated": true, "user": { "...": "..." } } +``` +or +```json +{ "authenticated": false } +``` + +### `POST /api/sso/logout` (auth domain) +Behavior: +- revokes current session in DB +- clears shared cookie on `.topdoglabs.com` + +Response: +```json +{ "success": true } +``` + +### `GET /api/sso/authorize?return_to=` (auth domain) +Behavior: +- if authenticated: redirect (302) to `return_to` +- if unauthenticated: redirect to login +- validate `return_to` against allowlist + +--- + +## Data Model (Supabase) +Use/standardize `sessions` table: +- `user_id` (uuid, fk) +- `token_hash` (text, unique) +- `created_at` (timestamptz) +- `expires_at` (timestamptz) +- `revoked_at` (timestamptz, nullable) + +Recommended optional fields: +- `last_seen_at` +- `ip` +- `user_agent` +- `remember_me` + +Indexes: +- `token_hash` +- `user_id` +- `expires_at` + +Security: +- store only SHA-256 hash of token +- never store raw session token + +--- + +## Vercel Subdomain Setup (Step-by-Step) +1. In each Vercel project, open `Settings -> Domains`. +2. Add the desired subdomain for that specific project: + - Mission Control project -> `mission.topdoglabs.com` + - Gantt project -> `gantt.topdoglabs.com` + - Auth project -> `auth.topdoglabs.com` +3. If domain DNS is managed by Vercel nameservers, Vercel usually auto-creates the needed records. +4. If DNS is external: + - create the exact DNS record shown by Vercel on the domain card + - subdomains are typically `CNAME` + - apex/root is typically `A` +5. Wait for each to show verified/valid in Vercel. +6. Keep the `*.vercel.app` URLs for testing, but use custom subdomains for production SSO. + +--- + +## Subdomain Knowledge (Quick Primer) +### What a subdomain is +- A subdomain is a child host under a parent domain. +- Example: + - parent/root domain: `topdoglabs.com` + - subdomains: `mission.topdoglabs.com`, `gantt.topdoglabs.com`, `auth.topdoglabs.com` + +### Why this matters for SSO +- Browsers only allow one site to share a cookie with another site when both are under the same parent domain and the cookie `Domain` is set to that parent. +- For your case: set cookie `Domain=.topdoglabs.com` so all `*.topdoglabs.com` apps can read/send it. +- `mission-control-rho-pink.vercel.app` and `gantt-board.vercel.app` cannot share a parent-domain cookie with each other. + +### DNS records you will see +- Subdomain to app mapping is commonly a `CNAME` record. +- Apex/root domain usually uses `A`/`ALIAS`/`ANAME` depending on DNS provider. +- In Vercel, always follow the exact record values shown in each project’s Domains panel. + +### SSL/TLS on subdomains +- Vercel provisions certificates for added custom domains/subdomains after verification. +- SSO cookies with `Secure` require HTTPS in production, so certificate readiness is required before final auth testing. + +### Cookie scope rules (important) +- `Domain=.topdoglabs.com`: cookie is sent to all subdomains under `topdoglabs.com`. +- `Domain=mission.topdoglabs.com` or no `Domain` set: cookie is host-only, not shared with other subdomains. +- `SameSite=Lax`: good default for first-party web app navigation between your subdomains. + +### Recommended host roles +- `auth.topdoglabs.com`: login, logout, session validation endpoints +- `mission.topdoglabs.com`: Mission Control app +- `gantt.topdoglabs.com`: Gantt Board app +- Optional static/marketing hosts can stay separate, but app surfaces participating in SSO should remain under `*.topdoglabs.com`. + +--- + +## Subdomain Rollout Checklist +1. Add `mission.topdoglabs.com` to Mission Control Vercel project. +2. Add `gantt.topdoglabs.com` to Gantt project. +3. Add `auth.topdoglabs.com` to auth project (or auth routes host). +4. Verify each domain shows valid configuration and active HTTPS. +5. Update environment variables: + - `AUTH_ORIGIN=https://auth.topdoglabs.com` + - `COOKIE_DOMAIN=.topdoglabs.com` +6. Deploy auth cookie changes with `Domain=.topdoglabs.com`. +7. Test login on one app, then open the other app in a new tab. +8. Test global logout propagation across both apps. + +--- + +## Common Subdomain Troubleshooting +1. Symptom: still getting separate logins. + - Check cookie `Domain` is `.topdoglabs.com`, not host-only. +2. Symptom: login works on one app but not another. + - Confirm both apps are truly on `*.topdoglabs.com`, not `*.vercel.app`. +3. Symptom: cookie not set in production. + - Confirm HTTPS is active and cookie has `Secure`. +4. Symptom: redirect loops. + - Check `return_to` allowlist and ensure auth/session endpoint trusts both app origins. +5. Symptom: logout not global. + - Ensure logout revokes DB session and clears cookie with same name + same domain/path attributes. + +--- + +## Reusable Pattern for Future Websites +Use this as the standard for any new web app that should join shared SSO. + +### Onboarding standard for a new app +1. Put the app on a subdomain under `*.topdoglabs.com` (for example `newapp.topdoglabs.com`). +2. Add middleware/route guard that checks `tdl_sso_session`. +3. If no valid session, redirect to `https://auth.topdoglabs.com/login?return_to=`. +4. On sign-out, call central logout endpoint (`POST /api/sso/logout`) instead of local-only logout. +5. Ensure app URLs are added to auth `return_to` allowlist. + +### Required environment variables (per app) +- `AUTH_ORIGIN=https://auth.topdoglabs.com` +- `COOKIE_NAME=tdl_sso_session` +- `COOKIE_DOMAIN=.topdoglabs.com` + +### Required auth behavior (per app) +- Do not create app-specific login cookie names for SSO surfaces. +- Do not set host-only session cookies for authenticated app pages. +- Treat auth service as source of truth for session validity. + +### Minimal integration contract +- App must be able to: + - redirect unauthenticated users to auth origin with `return_to` + - accept authenticated return and continue to requested page + - trigger global logout + - handle expired/revoked session as immediate signed-out state + +### Naming convention recommendation +- Domain pattern: `.topdoglabs.com` +- Session cookie: `tdl_sso_session` +- Auth host: `auth.topdoglabs.com` + +This keeps every future app consistent and avoids one-off auth logic. + +--- + +## Migration Plan +1. Move apps to custom subdomains. +2. Introduce shared cookie `tdl_sso_session`. +3. Update auth cookie set/clear to include `Domain=.topdoglabs.com`. +4. Centralize auth endpoints on `auth.topdoglabs.com`. +5. Update app middleware to redirect to auth domain with `return_to`. +6. Keep legacy app-local cookie compatibility for 7-14 days. +7. Remove legacy cookie logic after rollout. + +--- + +## Security Controls +1. Strict allowlist for `return_to` URLs (prevent open redirects). +2. CSRF protection on login/logout POST endpoints. +3. Session TTL: + - short session: 12 hours + - remember me: 30 days +4. Treat expired/revoked sessions as immediate logout. +5. Rotate session token on sensitive account actions. + +--- + +## Test Cases +1. Login once, open app A then app B, no second login prompt. +2. Deep-link directly into app B while logged in from app A. +3. Logout in app A, refresh app B, user is logged out. +4. Expired cookie redirects to auth login. +5. Revoked session is denied in all apps. +6. Invalid `return_to` is rejected and replaced by safe default. +7. Remember-me survives browser restart; non-remember does not. +8. Cookie flags validated in production (`Secure`, `HttpOnly`, domain-scoped). + +--- + +## Assumptions +1. Both apps remain in the same Supabase project/database. +2. Both apps can be served under `*.topdoglabs.com`. +3. Global logout means current browser session is invalidated across all apps. +4. Existing `mission_control_session` is replaced by `tdl_sso_session`.