SSO Login Support — Design Document
ARCHIVE — This document is historical reference only. It may contain outdated information. See docs/status.md for current project state.
Created: February 11, 2026 Status: Implemented Phase: 3c (Auth Enhancement)
Overview
Add social/SSO login alongside existing email/password authentication. Leverages GoTrue's built-in OAuth provider support — no auth system replacement needed.
Key decision: Keep Supabase GoTrue. It already supports 24 social providers and SAML 2.0 via environment variables. The existing JWT validation, user model, and auth middleware work unchanged regardless of login method.
Architecture
How GoTrue Social Login Works
1. Frontend → GET /api/auth/sso/google
2. Backend → returns GoTrue authorize URL
3. Frontend → redirects browser to GoTrue URL
4. GoTrue → redirects to Google consent screen
5. User → authorizes
6. Google → redirects to GoTrue /callback with code
7. GoTrue → exchanges code, creates/links user, issues JWT
8. GoTrue → redirects to frontend /auth/callback#access_token=...&refresh_token=...
9. Frontend → extracts tokens from URL fragment, saves, calls /api/auth/me
What Changes
| Layer | Change |
|---|---|
| Docker Compose | Add GOTRUE_EXTERNAL_* env vars per provider |
| Backend | New /api/auth/providers + /api/auth/sso/{provider} endpoints |
| Backend | "Find or create" morphee_user record on first social login |
| Frontend | Social login buttons on AuthForm |
| Frontend | /auth/callback route to handle GoTrue redirect |
What Does NOT Change
verify_token()— same JWT format regardless of login methodAuthUsermodel — no changesget_current_userdependency — works the same- WebSocket auth — same JWT
- Existing Google OAuth for Calendar/Gmail — separate concern, coexists
- Any backend service that consumes
AuthUser
Coexistence with Google API OAuth
The existing /api/oauth/google/* flow is for API access (Calendar/Gmail scopes). GoTrue Google login is for authentication (email/profile scopes). These are separate concerns:
- User signs in with Google (GoTrue) → gets a Morphee JWT
- User later connects Google Calendar (existing OAuth) → gets Google API tokens stored in vault
No conflict. Both flows use the same GOOGLE_CLIENT_ID but request different scopes and store tokens differently.
Provider Prioritization
Tier 1 — Must Have
| Provider | Env Prefix | Reasoning |
|---|---|---|
GOTRUE_EXTERNAL_GOOGLE_* | Most universal. Families, teachers, teams. Unifies with Calendar/Gmail integration | |
| Apple | GOTRUE_EXTERNAL_APPLE_* | Required for iOS App Store (Apple mandate). macOS desktop users |
| Microsoft | GOTRUE_EXTERNAL_AZURE_* | Large corporate/education base. Microsoft 365 teachers, Azure AD teams |
Tier 2 — Should Have
| Provider | Env Prefix | Reasoning |
|---|---|---|
| GitHub | GOTRUE_EXTERNAL_GITHUB_* | Developer/tech-savvy users, open source community |
| Discord | GOTRUE_EXTERNAL_DISCORD_* | Communities, teams, gaming groups |
| Slack | GOTRUE_EXTERNAL_SLACK_OIDC_* | Workplace teams already on Slack |
Tier 3 — Later / On Demand
| Provider | Reasoning |
|---|---|
| SAML 2.0 | Enterprise customers with Okta/Azure AD/OneLogin. Defer until enterprise demand |
| Declining relevance for auth. Privacy concerns clash with Morphee values | |
| Twitter/X | Niche audience, API instability |
Not Recommended
Spotify, Twitch, Figma, LinkedIn, GitLab — no alignment with Morphee's audience.
Implementation Steps
Step 1: GoTrue Provider Configuration (S)
Add environment variables to docker-compose.dev.yml and docker-compose.yml:
supabase-auth:
environment:
# ... existing config ...
# Google SSO
GOTRUE_EXTERNAL_GOOGLE_ENABLED: true
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_CLIENT_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: http://localhost:9999/callback
# Apple SSO
GOTRUE_EXTERNAL_APPLE_ENABLED: true
GOTRUE_EXTERNAL_APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
GOTRUE_EXTERNAL_APPLE_SECRET: ${APPLE_CLIENT_SECRET}
GOTRUE_EXTERNAL_APPLE_REDIRECT_URI: http://localhost:9999/callback
# Microsoft / Azure AD SSO
GOTRUE_EXTERNAL_AZURE_ENABLED: true
GOTRUE_EXTERNAL_AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
GOTRUE_EXTERNAL_AZURE_SECRET: ${AZURE_CLIENT_SECRET}
GOTRUE_EXTERNAL_AZURE_URL: https://login.microsoftonline.com/common
GOTRUE_EXTERNAL_AZURE_REDIRECT_URI: http://localhost:9999/callback
# URI allow list (add callback URLs)
GOTRUE_URI_ALLOW_LIST: "http://localhost:3000,http://localhost:5173,http://localhost:5173/auth/callback,tauri://localhost,http://tauri.localhost"
Validation: curl http://localhost:9999/settings | jq '.external' should show providers as enabled.
Step 2: Backend SSO Endpoints (S)
New file: backend/api/sso.py
# GET /api/auth/providers
# Returns: {"providers": ["google", "apple", "azure"]}
# Queries GoTrue /settings to discover which are enabled
# GET /api/auth/sso/{provider}
# Returns: {"authorize_url": "http://localhost:9999/authorize?provider=google&redirect_to=..."}
# Constructs the GoTrue authorize URL with proper redirect_to
# POST /api/auth/sso/callback
# Receives: {access_token, refresh_token} from frontend after GoTrue redirect
# Creates morphee_user record if first social login
# Returns: AuthResponse (same format as signin/signup)
Key implementation detail — morphee_user creation for social login:
Currently, morphee_user records are only created in AuthService.sign_up(). Social login users are created in GoTrue's auth.users table but NOT in public.users. The POST /api/auth/sso/callback endpoint (or a modification to verify_token()) must:
- Look up
public.usersbyauth_user_id - If not found, create a new record with
group_id=NULL(triggers onboarding) - Extract name from GoTrue user metadata (
user_metadata.full_nameoruser_metadata.name)
~15 new tests.
Step 3: Frontend Social Login UI + Callback (M)
AuthForm changes:
Add social login buttons below the email/password form:
┌─────────────────────────┐
│ Sign In │
│ │
│ [Email field] │
│ [Password field] │
│ [Sign In button] │
│ │
│ ─── or continue with ──│
│ │
│ [G] Sign in with Google│
│ [] Sign in with Apple │
│ [M] Sign in with Microsoft│
│ │
│ Don't have an account? │
└─────────────────────────┘
New route: /auth/callback
// Handles GoTrue redirect: /auth/callback#access_token=...&refresh_token=...
// 1. Extract tokens from URL fragment (window.location.hash)
// 2. POST to /api/auth/sso/callback with tokens
// 3. Save auth response via saveAuth()
// 4. Call /api/auth/me to get user info
// 5. Redirect to / (or /onboarding if no group)
useAuth hook extension:
const ssoLogin = useCallback(async (provider: string) => {
setLoading(true)
const { authorize_url } = await authApi.ssoAuthorize(provider)
window.location.href = authorize_url // Full-page redirect
}, [setLoading])
~15 new tests (unit + E2E).
Step 4: User Linking + Edge Cases (S)
- Same email, different method: GoTrue handles automatic user linking when
GOTRUE_SECURITY_MANUAL_LINKING_ENABLEDis false (default). User who signed up with email can later "Sign in with Google" if emails match. - Social login user without email: Some providers allow sign-in without email. Redirect to a "complete profile" page that asks for email + name.
- Social login → password setup: Offer "Set password" option in Settings so social login users can also use email/password.
- Tauri desktop callback: Configure
GOTRUE_URI_ALLOW_LISTto includetauri://localhost/auth/callbackandhttp://tauri.localhost/auth/callback. Tauri webview intercepts the redirect.
Step 5: Tier 2 Providers (XS each)
Once Steps 1-4 are done, adding each new provider is:
- Add
GOTRUE_EXTERNAL_{PROVIDER}_*env vars to docker-compose - Add a button + icon to AuthForm
- No backend changes needed (generic SSO endpoints handle all providers)
Step 6 (Future): SAML 2.0 for Enterprise (L)
- Enable
GOTRUE_SAML_ENABLED=true, generate RSA key pair - Admin API to register IdPs (per-group SAML configuration)
- Admin UI in Settings for group owners to configure their SAML IdP
- Domain-based routing: detect
user@company.com→ route to company's SAML IdP - Defer until enterprise demand materializes
Provider Setup Guides
Google
- Go to Google Cloud Console
- Create or select a project
- Navigate to APIs & Services > Credentials
- Click Create Credentials > OAuth 2.0 Client IDs
- Application type: Web application
- Authorized redirect URIs: add
http://localhost:9999/callback(dev) andhttps://auth.morphee.app/callback(prod) - Copy Client ID and Client Secret
- Set env vars:
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
Note: The same Google OAuth app can be used for both SSO login (via GoTrue) and Calendar/Gmail API access (via existing /api/oauth/google/*). GoTrue requests email + profile scopes; the Calendar/Gmail flow requests additional API scopes.
Apple
- Go to Apple Developer Portal
- Navigate to Certificates, Identifiers & Profiles > Identifiers
- Register a new App ID with "Sign In with Apple" capability enabled
- Navigate to Keys and create a new key with "Sign In with Apple" enabled
- Download the
.p8key file - Create a Services ID (this is the Client ID for web)
- Configure the Services ID:
- Domains:
localhost(dev),morphee.app(prod) - Return URLs:
http://localhost:9999/callback(dev),https://auth.morphee.app/callback(prod)
- Domains:
- Generate the client secret (JWT signed with your
.p8key):# Apple client secrets are short-lived JWTs. Use a script or library to generate.
# Required claims: iss (Team ID), iat, exp, aud (https://appleid.apple.com), sub (Services ID) - Set env vars:
APPLE_CLIENT_ID=your-services-id (e.g., app.morphee.signin)
APPLE_CLIENT_SECRET=your-generated-jwt
Note: Apple client secrets expire every 6 months. Automate regeneration or use a longer-lived approach.
Microsoft / Azure AD
- Go to Azure Portal
- Navigate to Azure Active Directory > App registrations
- Click New registration
- Name: "Morphee"
- Supported account types: Accounts in any organizational directory and personal Microsoft accounts (for broadest access)
- Redirect URI: Web >
http://localhost:9999/callback(dev) - Copy Application (client) ID
- Navigate to Certificates & secrets > New client secret
- Copy the Value (not the Secret ID)
- Set env vars:
AZURE_CLIENT_ID=your-application-id
AZURE_CLIENT_SECRET=your-client-secret-value
Note: Use https://login.microsoftonline.com/common as the Azure URL to support both personal Microsoft accounts and organizational (Azure AD) accounts.
GitHub
- Go to GitHub Developer Settings
- Click New OAuth App
- Application name: "Morphee"
- Homepage URL:
http://localhost:5173(dev) - Authorization callback URL:
http://localhost:9999/callback - Copy Client ID and generate a Client Secret
- Set env vars:
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
Discord
- Go to Discord Developer Portal
- Click New Application
- Navigate to OAuth2
- Add redirect:
http://localhost:9999/callback - Copy Client ID and Client Secret
- Set env vars:
DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret
Slack
- Go to Slack API
- Click Create New App > From scratch
- Navigate to OAuth & Permissions
- Add redirect URL:
http://localhost:9999/callback - Under Scopes > User Token Scopes, add:
openid,profile,email - Navigate to Basic Information
- Copy Client ID and Client Secret
- Set env vars:
SLACK_CLIENT_ID=your-client-id
SLACK_CLIENT_SECRET=your-client-secret
Note: Use the OIDC variant (GOTRUE_EXTERNAL_SLACK_OIDC_*) for Slack — it's the newer, more reliable flow.
Risks & Mitigations
| Risk | Mitigation |
|---|---|
| GoTrue version | Upgraded to v2.185.0 (Feb 2026). Python client migrated from gotrue to supabase_auth |
| Same email across providers | GoTrue automatic user linking handles this (default behavior) |
| Social login user has no password | Keep them social-only, or offer "set password" in Settings |
| Popup blockers | Using full-page redirect (not popup) — no blocker issues |
| Tauri desktop callback URL | Add tauri://localhost to GOTRUE_URI_ALLOW_LIST |
| Apple client secret expiry | Document 6-month rotation; automate in production |
| Provider API changes | GoTrue community maintains provider adapters; track GoTrue releases |
Effort Summary
| Step | Description | Size |
|---|---|---|
| 1 | GoTrue config (Google/Apple/Microsoft) | S |
| 2 | Backend SSO endpoints + user creation | S |
| 3 | Frontend social buttons + callback | M |
| 4 | User linking edge cases | S |
| 5 | Tier 2 providers (GitHub/Discord/Slack) | XS each |
| 6 | SAML 2.0 (future) | L |
Total Tier 1 (Steps 1-4): Medium