Authentication Architecture for customers.dev

Overview

The customers.dev application uses Clerk's satellite domain architecture, authenticating through auth.do.dev as the primary authentication domain. This approach provides centralized authentication while maintaining a focused customer management application. The architecture uses Convex for data storage with Role-Based Access Control (RBAC) for user permissions.

Architecture Diagram

graph TB
    subgraph "Primary Auth Domain"
        AUTH[auth.do.dev<br/>Primary Clerk Domain]
        CLERK_PRIMARY[Clerk Authentication]
    end

    subgraph "Satellite Domain"
        CUSTOMERS[customers.dev<br/>Port 3015<br/>Customer Management]
    end

    subgraph "Data Layer"
        CONVEX[Convex Cloud<br/>Single Project]
        CUSTOMERS_DATA[Customers]
        LISTS_DATA[Lists]
        USERS_DATA[Users]
        RBAC[RBAC System]
    end

    subgraph "External Services"
        CLERK_API[Clerk API]
        OAUTH[OAuth Providers<br/>Google, GitHub]
    end

    AUTH --> CLERK_PRIMARY
    CLERK_PRIMARY --> CLERK_API
    CLERK_API --> OAUTH

    CUSTOMERS --> AUTH
    CUSTOMERS --> CONVEX

    CONVEX --> CUSTOMERS_DATA
    CONVEX --> LISTS_DATA
    CONVEX --> USERS_DATA
    CONVEX --> RBAC

Core Components

1. Primary Authentication Domain (auth.do.dev)

Purpose: Central authentication hub managed by the do.dev infrastructure

Key Features:

  • Handles all authentication flows (sign-in, sign-up, sign-out)
  • Manages OAuth integrations (Google, GitHub)
  • Email verification and OTP
  • User onboarding
  • Session management across satellite domains

Technology Stack:

  • Next.js with App Router
  • Clerk (Primary Domain Mode)
  • Managed by do.dev infrastructure

2. Satellite Domain (customers.dev)

Purpose: Customer management application that relies on auth.do.dev for authentication

Key Features:

  • Seamless SSO through auth.do.dev
  • Shared session with do.dev authentication
  • Role-based access control
  • Protected routes with automatic redirects to auth.do.dev
  • Local user context from Clerk

Technology Stack:

  • Next.js 15.4+ with App Router
  • Clerk (Satellite Mode)
  • TypeScript 5.7.3 (Strict Mode)
  • Tailwind CSS v4
  • Convex Cloud for backend

3. Data Layer (Convex)

Purpose: Serverless backend for all data and API needs

Key Features:

  • Single Convex project (no multi-project setup)
  • Real-time queries and subscriptions
  • HTTP Actions for REST API
  • Convex auth integration with Clerk
  • Schema-defined TypeScript types

Data Models:

  • Customers: Customer information and metadata
  • Lists: Customer list management (e.g., waitlists)
  • Users: User profiles synced from Clerk
  • Roles: RBAC system for permissions

Authentication Flow

Sign-In Flow

  1. User visits customers.dev/dashboard
  2. Middleware detects unauthenticated request
  3. Redirects to auth.do.dev/sign-in?redirect_url=customers.dev/dashboard
  4. User authenticates via Clerk on auth.do.dev
  5. Clerk syncs session to customers.dev (satellite)
  6. User redirected back to customers.dev/dashboard
  7. Convex validates Clerk JWT and loads user data

Sign-Out Flow

  1. User clicks sign out on customers.dev
  2. Clerk signs out user across all domains
  3. Session cleared on customers.dev
  4. User redirected to public page

New User Registration

  1. User redirected to auth.do.dev/sign-up
  2. User completes registration (email, OAuth, etc.)
  3. Clerk creates user account
  4. Webhook triggers Convex user creation
  5. Default role assigned via Convex
  6. User redirected to customers.dev/dashboard

Role-Based Access Control (RBAC)

Role System

Implemented in Convex with the following roles:

  • admin: Full access to all features and data
  • user: Standard customer management access
  • viewer: Read-only access to customer data
  • waitlist: Limited access for new users pending approval

See ROLES.md for complete role definitions.

Permission Checking

// Example: Checking permissions in Convex query
export const getCustomers = query({
  handler: async (ctx) => {
    const user = await ctx.auth.getUserIdentity()
    if (!user) throw new Error("Not authenticated")

    const userRoles = await ctx.db
      .query("users")
      .filter(q => q.eq(q.field("clerkId"), user.subject))
      .first()

    if (!userRoles || !["admin", "user"].includes(userRoles.role)) {
      throw new Error("Insufficient permissions")
    }

    return await ctx.db.query("customers").collect()
  }
})

Environment Configuration

Development Environment

# Clerk Configuration (Satellite Mode)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
CLERK_SECRET_KEY=sk_test_***

# Satellite configuration pointing to local auth domain
NEXT_PUBLIC_CLERK_IS_SATELLITE=true
NEXT_PUBLIC_CLERK_DOMAIN=localhost:3005
NEXT_PUBLIC_CLERK_SIGN_IN_URL=http://localhost:3005/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=http://localhost:3005/sign-up
CLERK_SATELLITE_URL=http://localhost:3015

# Redirect URLs
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=http://localhost:3015/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=http://localhost:3015/dashboard

# Convex (Single Project)
NEXT_PUBLIC_CONVEX_URL=https://impartial-panda-219.convex.cloud
CONVEX_DEPLOY_KEY=dev:impartial-panda-219|***

Production Environment

# Clerk Configuration (Satellite Mode)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_***
CLERK_SECRET_KEY=sk_live_***

# Satellite configuration pointing to production auth domain
NEXT_PUBLIC_CLERK_IS_SATELLITE=true
NEXT_PUBLIC_CLERK_DOMAIN=auth.do.dev
NEXT_PUBLIC_CLERK_SIGN_IN_URL=https://auth.do.dev/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=https://auth.do.dev/sign-up
CLERK_SATELLITE_URL=https://customers.dev

# Redirect URLs
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=https://customers.dev/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=https://customers.dev/dashboard

# Convex (Single Project)
NEXT_PUBLIC_CONVEX_URL=https://impartial-panda-219.convex.cloud
CONVEX_DEPLOY_KEY=prod:***

Security Considerations

Session Management

  • Sessions managed by Clerk across all domains
  • JWT tokens validated by both Clerk and Convex
  • Automatic session refresh handled by Clerk SDK

Data Access

  • All Convex queries validate Clerk authentication
  • Role-based permissions enforced at database level
  • User identity from Clerk JWT used for data filtering

API Security

  • Convex HTTP Actions use API key authentication (separate from user auth)
  • Rate limiting implemented in Convex actions
  • Input validation using Convex validators

Deployment Architecture

Frontend (Vercel)

  • Automatic deployments on push to main
  • Environment variables configured in Vercel dashboard
  • Edge functions for middleware
  • Global CDN distribution

Backend (Convex Cloud)

  • Automatic deployments via Convex CLI
  • Real-time data synchronization
  • Serverless HTTP Actions for REST API
  • Built-in authentication with Clerk

Troubleshooting

Common Issues

  1. Infinite Redirect Loop

    • Ensure CLERK_SATELLITE_URL matches actual domain
    • Verify NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL is set correctly
  2. Session Not Syncing

    • Verify both domains use the same Clerk publishable key
    • Check that isSatellite is set to true in ClerkProvider
    • Confirm domain points to auth.do.dev
  3. Convex Auth Fails

    • Ensure convex/auth.config.ts points to correct primary domain
    • Verify Clerk JWT is being passed to Convex
    • Check ConvexProviderWithClerk is being used (not ConvexProvider)

On this page