Skip to main content

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

LayerChange
Docker ComposeAdd GOTRUE_EXTERNAL_* env vars per provider
BackendNew /api/auth/providers + /api/auth/sso/{provider} endpoints
Backend"Find or create" morphee_user record on first social login
FrontendSocial login buttons on AuthForm
Frontend/auth/callback route to handle GoTrue redirect

What Does NOT Change

  • verify_token() — same JWT format regardless of login method
  • AuthUser model — no changes
  • get_current_user dependency — 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

ProviderEnv PrefixReasoning
GoogleGOTRUE_EXTERNAL_GOOGLE_*Most universal. Families, teachers, teams. Unifies with Calendar/Gmail integration
AppleGOTRUE_EXTERNAL_APPLE_*Required for iOS App Store (Apple mandate). macOS desktop users
MicrosoftGOTRUE_EXTERNAL_AZURE_*Large corporate/education base. Microsoft 365 teachers, Azure AD teams

Tier 2 — Should Have

ProviderEnv PrefixReasoning
GitHubGOTRUE_EXTERNAL_GITHUB_*Developer/tech-savvy users, open source community
DiscordGOTRUE_EXTERNAL_DISCORD_*Communities, teams, gaming groups
SlackGOTRUE_EXTERNAL_SLACK_OIDC_*Workplace teams already on Slack

Tier 3 — Later / On Demand

ProviderReasoning
SAML 2.0Enterprise customers with Okta/Azure AD/OneLogin. Defer until enterprise demand
FacebookDeclining relevance for auth. Privacy concerns clash with Morphee values
Twitter/XNiche audience, API instability

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:

  1. Look up public.users by auth_user_id
  2. If not found, create a new record with group_id=NULL (triggers onboarding)
  3. Extract name from GoTrue user metadata (user_metadata.full_name or user_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_ENABLED is 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_LIST to include tauri://localhost/auth/callback and http://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:

  1. Add GOTRUE_EXTERNAL_{PROVIDER}_* env vars to docker-compose
  2. Add a button + icon to AuthForm
  3. 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

  1. Go to Google Cloud Console
  2. Create or select a project
  3. Navigate to APIs & Services > Credentials
  4. Click Create Credentials > OAuth 2.0 Client IDs
  5. Application type: Web application
  6. Authorized redirect URIs: add http://localhost:9999/callback (dev) and https://auth.morphee.app/callback (prod)
  7. Copy Client ID and Client Secret
  8. 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

  1. Go to Apple Developer Portal
  2. Navigate to Certificates, Identifiers & Profiles > Identifiers
  3. Register a new App ID with "Sign In with Apple" capability enabled
  4. Navigate to Keys and create a new key with "Sign In with Apple" enabled
  5. Download the .p8 key file
  6. Create a Services ID (this is the Client ID for web)
  7. Configure the Services ID:
    • Domains: localhost (dev), morphee.app (prod)
    • Return URLs: http://localhost:9999/callback (dev), https://auth.morphee.app/callback (prod)
  8. Generate the client secret (JWT signed with your .p8 key):
    # 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)
  9. 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

  1. Go to Azure Portal
  2. Navigate to Azure Active Directory > App registrations
  3. Click New registration
  4. Name: "Morphee"
  5. Supported account types: Accounts in any organizational directory and personal Microsoft accounts (for broadest access)
  6. Redirect URI: Web > http://localhost:9999/callback (dev)
  7. Copy Application (client) ID
  8. Navigate to Certificates & secrets > New client secret
  9. Copy the Value (not the Secret ID)
  10. 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

  1. Go to GitHub Developer Settings
  2. Click New OAuth App
  3. Application name: "Morphee"
  4. Homepage URL: http://localhost:5173 (dev)
  5. Authorization callback URL: http://localhost:9999/callback
  6. Copy Client ID and generate a Client Secret
  7. Set env vars:
    GITHUB_CLIENT_ID=your-client-id
    GITHUB_CLIENT_SECRET=your-client-secret

Discord

  1. Go to Discord Developer Portal
  2. Click New Application
  3. Navigate to OAuth2
  4. Add redirect: http://localhost:9999/callback
  5. Copy Client ID and Client Secret
  6. Set env vars:
    DISCORD_CLIENT_ID=your-client-id
    DISCORD_CLIENT_SECRET=your-client-secret

Slack

  1. Go to Slack API
  2. Click Create New App > From scratch
  3. Navigate to OAuth & Permissions
  4. Add redirect URL: http://localhost:9999/callback
  5. Under Scopes > User Token Scopes, add: openid, profile, email
  6. Navigate to Basic Information
  7. Copy Client ID and Client Secret
  8. 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

RiskMitigation
GoTrue versionUpgraded to v2.185.0 (Feb 2026). Python client migrated from gotrue to supabase_auth
Same email across providersGoTrue automatic user linking handles this (default behavior)
Social login user has no passwordKeep them social-only, or offer "set password" in Settings
Popup blockersUsing full-page redirect (not popup) — no blocker issues
Tauri desktop callback URLAdd tauri://localhost to GOTRUE_URI_ALLOW_LIST
Apple client secret expiryDocument 6-month rotation; automate in production
Provider API changesGoTrue community maintains provider adapters; track GoTrue releases

Effort Summary

StepDescriptionSize
1GoTrue config (Google/Apple/Microsoft)S
2Backend SSO endpoints + user creationS
3Frontend social buttons + callbackM
4User linking edge casesS
5Tier 2 providers (GitHub/Discord/Slack)XS each
6SAML 2.0 (future)L

Total Tier 1 (Steps 1-4): Medium