280 lines
9.5 KiB
Markdown
280 lines
9.5 KiB
Markdown
# 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=<url>` (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.twisteddevices.com` and `gantt-board.twisteddevices.com` 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=<current_url>`.
|
||
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: `<app>.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`.
|