Skip to main content

Authentication Flow

GospeLib uses Clerk as the authentication provider in Phase 1. The auth service is a thin Go wrapper that translates Clerk events into our domain and provides a stable internal interface — swapping Clerk later changes only the wrapper, not anything downstream.

Flow Diagram

sequenceDiagram
participant User as User (Mobile/Web)
participant Clerk as Clerk Auth
participant App as GospeLib App
participant GW as API Gateway
participant Svc as Downstream Service
participant PG as PostgreSQL

User->>Clerk: Sign in (email, magic link, or Google OAuth)
Clerk-->>App: JWT (15 min) + refresh token (30 days)
App->>GW: API request + Authorization: Bearer <jwt>
GW->>GW: Validate JWT signature (JWKS cached)
GW->>GW: Extract claims: userId, email, sessionId
GW->>GW: Inject headers: X-User-Id, X-User-Plan
GW->>Svc: Forward request with injected headers
Svc->>PG: Query using X-User-Id
Svc-->>GW: Response
GW-->>App: Response

Token Lifecycle

TokenLifetimeStorageRefresh Strategy
JWT (access token)15 minutesIn-memory (app)Clerk SDK auto-refreshes before expiry
Refresh token30 daysSecure storage (Keychain/SecureStore)Used to obtain new JWT silently
Session30 days (configurable)Clerk manages server-sideRevocable via Clerk dashboard

Gateway JWT Validation

The gateway validates every JWT against Clerk's JWKS (JSON Web Key Set) endpoint:

  1. JWKS caching — keys are cached in memory and refreshed every 5 minutes
  2. Signature verification — validates the JWT's RS256 signature against Clerk's public key
  3. Claims extraction — extracts userId, email, and sessionId from the JWT payload
  4. Header injection — sets X-User-Id and X-User-Plan headers on the forwarded request

Downstream services trust X-User-Id without re-validating the JWT because they are only reachable via the gateway (not exposed publicly).

Clerk Webhook Sync

When users sign up or update their profiles in Clerk, webhook events sync the data to PostgreSQL:

sequenceDiagram
participant Clerk
participant GW as Gateway
participant Auth as Auth Service
participant PG as PostgreSQL
participant Redis as Redis Stream

Clerk->>GW: POST /api/v1/auth/webhook (user.created)
GW->>Auth: Forward (no auth required for webhooks)
Auth->>Auth: Verify Clerk webhook signature
Auth->>PG: UPSERT gl_users (clerk_id, email, name)
Auth->>Redis: XADD gl:events:users {user.created}
Auth-->>GW: 200 OK

Webhook signature verification ensures the request actually came from Clerk.

Auth Service Responsibilities

The auth service (Go/Chi, port 8200) handles:

  • Webhook receiveruser.created, user.updated, session.created events from Clerk
  • User sync — Clerk user records → PostgreSQL gl_users table
  • Profile API/users/{id} for other services to fetch user profiles
  • Entitlement join/users/{id}/entitlements joins user record with billing plan

Phase Progression

PhaseAuth Strategy
Phase 1Clerk SaaS (free to 10K MAU)
Phase 2Clerk SaaS (paid tier)
Phase 3Option to migrate to custom auth (the wrapper makes this safe)

Why Clerk in Phase 1? Building production-grade auth (MFA, magic links, social auth, session management, device trust) takes 2–6 months. Clerk does this for $0–$100/month at our scale, saving months of development time.