Internal DocumentationArchived DocumentationDo dev legacyArchiveClerk migration

Authentication Architecture Design

Overview

The do.dev application uses a single-domain architecture with Clerk.dev for authentication and Convex for data storage. This streamlined approach provides centralized authentication within the dodev application while maintaining real-time data capabilities. The architecture supports multi-tenant organizations, teams, and comprehensive Role-Based Access Control (RBAC) with a default "waitlist" role for new users.

Architecture Diagram

graph TB
    subgraph "Single Domain Application"
        DODEV[do.dev<br/>Port 3005<br/>Main Application]
        CLERK_AUTH[Clerk Authentication]
    end
    
    subgraph "Data Layer"
        CONVEX[Convex Database]
        STORAGE[File Storage]
        RBAC[RBAC System]
        ORG[Organizations]
        TEAMS[Teams]
    end
    
    subgraph "External Services"
        CLERK_API[Clerk API]
        OAUTH[OAuth Providers<br/>Google, GitHub]
    end
    
    subgraph "Management Layer"
        ADMIN[Admin Dashboard<br/>Role Management]
        ORGMGMT[Organization Management]
    end
    
    DODEV --> CLERK_AUTH
    CLERK_AUTH --> CLERK_API
    CLERK_API --> OAUTH
    
    DODEV --> CONVEX
    DODEV --> ADMIN
    ADMIN --> RBAC
    ADMIN --> ORGMGMT
    
    CONVEX --> STORAGE
    CONVEX --> ORG
    CONVEX --> TEAMS
    CONVEX --> RBAC

Core Components

1. Single Domain Application (do.dev)

Purpose: Complete authentication and application functionality within one domain

Key Features:

  • Handles all authentication flows (sign-in, sign-up, sign-out)
  • Manages OAuth integrations (Google, GitHub)
  • Email OTP verification
  • User onboarding and role assignment
  • Session management within the application
  • Organization and team management
  • Role and permission administration
  • User invitation and onboarding
  • Service monitoring and analytics

Technology Stack:

  • Next.js 15.2.3 with App Router
  • Clerk.dev (Single Domain Mode)
  • TypeScript 5.7.3
  • Tailwind CSS v4

Configuration:

// Single domain configuration
{
  publishableKey: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
  secretKey: process.env.CLERK_SECRET_KEY,
  domain: process.env.NEXT_PUBLIC_CLERK_DOMAIN || "localhost:3005",
  signInUrl: "/sign-in",
  signUpUrl: "/sign-up",
  afterSignInUrl: "/dashboard",
  afterSignUpUrl: "/onboarding",
  // Organization context
  organizationId: process.env.NEXT_PUBLIC_ORG_ID,
  allowPersonalWorkspace: true
}

2. Data Synchronization Layer

Purpose: Bridge between Clerk authentication and Convex data storage

Key Components:

  • User metadata synchronization
  • Role mapping between systems
  • Profile data management
  • File storage integration

Authentication Flows

1. Sign-In Flow

sequenceDiagram
    participant User
    participant App as do.dev App
    participant Clerk as Clerk API
    participant Convex
    
    User->>App: Access protected route
    App->>User: Show sign-in form
    User->>App: Submit credentials
    App->>Clerk: Authenticate user
    Clerk->>App: Return session
    App->>Convex: Sync user data
    App->>User: Show authenticated content

2. Sign-Out Flow

sequenceDiagram
    participant User
    participant App as do.dev App
    participant Clerk as Clerk API
    
    User->>App: Click sign out
    App->>Clerk: Revoke session
    App->>App: Clear cookies
    App->>User: Show signed out state

3. OAuth Flow

sequenceDiagram
    participant User
    participant App as do.dev App
    participant OAuth as OAuth Provider
    participant Clerk as Clerk API
    
    User->>App: Click OAuth sign-in
    App->>OAuth: Redirect to provider
    OAuth->>User: Request authorization
    User->>OAuth: Approve access
    OAuth->>App: Return with code
    App->>Clerk: Exchange for tokens
    Clerk->>App: Create session
    App->>User: Show authenticated content

API Design

Authentication API Endpoints

Application (do.dev)

// Sign-out API
POST /api/sign-out
Response: Redirect to home page

// Onboarding API
POST /api/onboarding
Body: { appId: string }
Response: { success: boolean, user: ClerkUser }

// Webhook Handler
POST /api/webhooks/clerk
Body: ClerkWebhookEvent
Response: { success: boolean }

// Profile Update API
POST /api/profile
Body: { displayName: string, bio: string }
Response: { success: boolean, user: ClerkUser }

// User Context API
GET /api/user
Response: { user: AuthUser | null }

// Role Check API
GET /api/auth/role
Query: { role: string }
Response: { hasRole: boolean }

Data Models

// Clerk User Metadata Structure
interface ClerkPublicMetadata {
  appId?: string;          // Application identifier
  custId?: string;         // Customer ID from Convex
  userId?: string;         // User ID from Convex
  roles?: string[];        // User roles (default: ["waitlist"])
  organizationRoles?: OrganizationRole[]; // Organization-specific roles
  verified?: boolean;      // Email verification status
  lastLoginAt?: number;    // Last login timestamp
  onboardingCompleted?: boolean;
  bio?: string;           // User bio
  defaultOrganizationId?: string; // Default organization
}

// Organization Role Structure
interface OrganizationRole {
  organizationId: string;
  roles: string[];
  teamIds?: string[];
  permissions?: string[];
}

// Unified Auth User Interface
interface AuthUser {
  id: string;
  email: string | null;
  name: string | null;
  givenName: string | null;
  familyName: string | null;
  image: string | null;
  roles: string[];         // Global roles
  organizationRoles?: OrganizationRole[]; // Org-specific roles
  currentOrganization?: Organization;
  appId?: string;
  custId?: string;
  userId?: string;
  [key: string]: any;
}

// Organization Structure
interface Organization {
  id: string;
  name: string;
  slug: string;
  ownerId: string;
  plan: 'free' | 'starter' | 'pro' | 'enterprise';
  features: string[];
  memberCount: number;
  teamCount: number;
  createdAt: Date;
  updatedAt: Date;
}

// Team Structure
interface Team {
  id: string;
  organizationId: string;
  name: string;
  description?: string;
  memberIds: string[];
  permissions: string[];
  createdAt: Date;
  updatedAt: Date;
}

// Role Definitions
enum GlobalRole {
  // Default role for all new users
  WAITLIST = "waitlist",
  
  // Approved users
  USER = "user",
  
  // Platform roles
  STAFF = "staff",
  ADMIN = "admin",
  SUPER_ADMIN = "super_admin",
  
  // Legacy roles
  DO_ROOT = "do-root",
  DO_STAFF = "do-staff",
  WAITLIST_APPROVED = "waitlist-approved"
}

enum OrganizationRole {
  // Organization management
  OWNER = "org:owner",
  ADMIN = "org:admin",
  MEMBER = "org:member",
  GUEST = "org:guest",
  
  // Team roles
  TEAM_ADMIN = "team:admin",
  TEAM_MEMBER = "team:member",
  TEAM_VIEWER = "team:viewer",
  
  // Service-specific roles
  BILLING_ADMIN = "billing:admin",
  DEVELOPER = "developer",
  CONTENT_EDITOR = "content:editor"
}

// Permission Structure
interface Permission {
  id: string;
  name: string;
  resource: string;
  actions: string[];
  conditions?: PermissionCondition[];
}

interface PermissionCondition {
  type: 'organization' | 'team' | 'resource';
  match: 'equals' | 'contains' | 'startsWith';
  value: string;
}

Component Architecture

Auth Hook Interface

interface UseAuthReturn {
  // User data
  user: AuthUser | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  
  // Auth actions
  login: (options?: any) => Promise<void>;
  logout: () => Promise<void>;
  register: (options?: any) => Promise<void>;
  getToken: () => Promise<string | undefined>;
  
  // Role checks
  hasRole: (role: string) => boolean;
  hasAnyRole: (roles: string[]) => boolean;
  hasAllRoles: (roles: string[]) => boolean;
  isAdmin: boolean;
  isStaff: boolean;
}

Provider Components

// App Providers Structure
<ClerkProvider
  publishableKey={CLERK_PUBLISHABLE_KEY}
  signInUrl={CLERK_SIGN_IN_URL}
  signUpUrl={CLERK_SIGN_UP_URL}
  afterSignInUrl={CLERK_SIGN_IN_FALLBACK_REDIRECT_URL}
  afterSignUpUrl={CLERK_SIGN_UP_FALLBACK_REDIRECT_URL}
>
  <ConvexProvider client={convexClient}>
    <PostAuthHandler>
      <ConditionalLayoutWrapper>
        {children}
      </ConditionalLayoutWrapper>
    </PostAuthHandler>
  </ConvexProvider>
</ClerkProvider>

Security Considerations

1. Session Management

  • Sessions are managed by Clerk within the single domain
  • Session validation occurs on each protected route access
  • Session tokens are httpOnly cookies
  • Automatic session refresh with token rotation

2. CORS Configuration

  • Single domain application eliminates cross-origin concerns
  • Standard same-origin security policies apply
  • Strict origin validation for API endpoints

3. Environment Variables

# Single Domain Application
CLERK_SECRET_KEY=sk_test_***
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
NEXT_PUBLIC_CLERK_DOMAIN=localhost:3005
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/onboarding

4. Middleware Protection

// Protected route patterns
const isProtectedRoute = createRouteMatcher([
  '/dashboard(.*)',
  '/profile(.*)',
  '/organization(.*)',
  '/admin(.*)',
])

// Automatic redirect for unauthenticated users
if (isProtectedRoute(request) && !userId) {
  return NextResponse.redirect(authUrl)
}

Implementation Status

Phase 1: Infrastructure Setup ✅

  1. Set up Clerk account and configure single domain
  2. Create do.dev application with Clerk integration
  3. Implement basic authentication flows
  4. Configure OAuth providers (Google, GitHub)

Phase 2: Data Migration ✅

  1. Map Convex users to Clerk users
  2. Migrate user roles and permissions
  3. Sync profile data
  4. Maintain backward compatibility

Phase 3: Feature Parity ✅

  1. Implement all authentication features
  2. Update all UI components
  3. Test all user flows
  4. Performance optimization

Phase 4: Production Ready ✅

  1. Remove old Convex auth code
  2. Update documentation
  3. Single domain deployment
  4. Production environment configured

Performance Optimizations

1. Caching Strategy

  • Cache user data within the application
  • Use SWR for data fetching
  • Implement optimistic updates

2. Bundle Size

  • Lazy load auth components
  • Tree-shake unused Clerk features
  • Minimize client-side auth logic

3. Network Optimization

  • Minimize unnecessary requests
  • Use HTTP/2 for parallel requests
  • Implement request batching

Monitoring and Analytics

1. Key Metrics

  • Authentication success rate
  • Session duration
  • Authentication flow time
  • API response times

2. Error Tracking

  • Failed authentication attempts
  • Session validation errors
  • API endpoint failures
  • Authentication flow issues

3. User Analytics

  • Login frequency
  • Authentication methods used
  • Role distribution
  • Geographic distribution

Role-Based Access Control (RBAC) System

RBAC Architecture

graph TB
    subgraph "User Registration"
        NEW[New User] --> WAITLIST[Waitlist Role]
        WAITLIST --> REVIEW[Admin Review]
        REVIEW --> APPROVED[User Role]
    end
    
    subgraph "Permission Hierarchy"
        SUPER[Super Admin]
        ADMIN[Platform Admin]
        STAFF[Platform Staff]
        ORG_OWNER[Org Owner]
        ORG_ADMIN[Org Admin]
        TEAM_ADMIN[Team Admin]
        MEMBER[Member]
        GUEST[Guest]
    end
    
    subgraph "Resource Access"
        PLATFORM[Platform Resources]
        ORG_RES[Organization Resources]
        TEAM_RES[Team Resources]
        USER_RES[User Resources]
    end
    
    SUPER --> PLATFORM
    ADMIN --> PLATFORM
    STAFF --> PLATFORM
    ORG_OWNER --> ORG_RES
    ORG_ADMIN --> ORG_RES
    TEAM_ADMIN --> TEAM_RES
    MEMBER --> USER_RES

Default Waitlist Role

All new users are automatically assigned the "waitlist" role:

// New user creation
const createNewUser = async (email: string) => {
  const user = await clerkClient.users.createUser({
    emailAddress: [email],
    publicMetadata: {
      roles: ['waitlist'],
      onboardingCompleted: false,
      createdAt: Date.now()
    }
  })
  
  // Notify admins for review
  await notifyAdminsOfNewUser(user)
  
  return user
}

Role Management Dashboard

The do.dev main dashboard includes comprehensive role management:

// Dashboard components
interface RoleManagementDashboard {
  // Role CRUD operations
  createRole: (role: RoleDefinition) => Promise<Role>
  updateRole: (roleId: string, updates: Partial<Role>) => Promise<Role>
  deleteRole: (roleId: string) => Promise<void>
  
  // Permission management
  assignPermissions: (roleId: string, permissions: string[]) => Promise<void>
  revokePermissions: (roleId: string, permissions: string[]) => Promise<void>
  
  // User role assignment
  assignUserRole: (userId: string, roleId: string, scope?: RoleScope) => Promise<void>
  revokeUserRole: (userId: string, roleId: string, scope?: RoleScope) => Promise<void>
  
  // Waitlist management
  approveWaitlistUser: (userId: string) => Promise<void>
  rejectWaitlistUser: (userId: string, reason?: string) => Promise<void>
  bulkApproveUsers: (userIds: string[]) => Promise<void>
}

interface RoleScope {
  type: 'global' | 'organization' | 'team'
  organizationId?: string
  teamId?: string
}

Organization and Team Management

Organization Structure

// Organization management API
interface OrganizationAPI {
  // Organization CRUD
  createOrganization: (data: CreateOrgData) => Promise<Organization>
  updateOrganization: (orgId: string, updates: Partial<Organization>) => Promise<Organization>
  deleteOrganization: (orgId: string) => Promise<void>
  
  // Member management
  inviteMember: (orgId: string, email: string, role: string) => Promise<Invitation>
  removeMember: (orgId: string, userId: string) => Promise<void>
  updateMemberRole: (orgId: string, userId: string, role: string) => Promise<void>
  
  // Team management
  createTeam: (orgId: string, team: CreateTeamData) => Promise<Team>
  updateTeam: (teamId: string, updates: Partial<Team>) => Promise<Team>
  deleteTeam: (teamId: string) => Promise<void>
  
  // Settings
  updateOrgSettings: (orgId: string, settings: OrgSettings) => Promise<void>
  updateBilling: (orgId: string, plan: string) => Promise<void>
}

Team Collaboration

// Team features
interface TeamFeatures {
  // Team membership
  addTeamMember: (teamId: string, userId: string, role: string) => Promise<void>
  removeTeamMember: (teamId: string, userId: string) => Promise<void>
  
  // Team resources
  shareResource: (teamId: string, resourceId: string, permissions: string[]) => Promise<void>
  unshareResource: (teamId: string, resourceId: string) => Promise<void>
  
  // Team communication
  createTeamChannel: (teamId: string, channel: ChannelData) => Promise<Channel>
  postTeamUpdate: (teamId: string, update: TeamUpdate) => Promise<void>
}

Multi-Tenant Architecture

// Organization context in requests
interface OrganizationContext {
  currentOrganization: Organization | null
  availableOrganizations: Organization[]
  personalWorkspace: boolean
  
  // Organization switching
  switchOrganization: (orgId: string) => Promise<void>
  createPersonalOrganization: () => Promise<Organization>
  
  // Context-aware permissions
  hasOrgPermission: (permission: string) => boolean
  hasTeamPermission: (teamId: string, permission: string) => boolean
}

Dashboard Integration

Role Management UI

// React components for role management
export function RoleManagementDashboard() {
  return (
    <div>
      <WaitlistQueue />
      <RolesList />
      <PermissionMatrix />
      <UserRoleAssignments />
    </div>
  )
}

export function WaitlistQueue() {
  // Shows users with waitlist role
  // Bulk approve/reject actions
  // User details and registration info
}

export function RoleCreator() {
  // Form to create custom roles
  // Permission selection
  // Scope definition (global/org/team)
}

export function PermissionMatrix() {
  // Visual matrix of roles vs permissions
  // Drag-and-drop permission assignment
  // Inheritance visualization
}

Organization Management UI

// Organization dashboard components
export function OrganizationDashboard() {
  return (
    <div>
      <OrganizationSwitcher />
      <OrganizationSettings />
      <TeamManagement />
      <MemberInvitations />
      <BillingManagement />
    </div>
  )
}

export function OrganizationSwitcher() {
  // Dropdown to switch between organizations
  // Create new organization option
  // Personal workspace toggle
}

export function TeamCreator() {
  // Form to create teams
  // Member selection
  // Permission assignment
}

Migration Path

Existing Users to Organizations

// Migration strategy
const migrateExistingUsers = async () => {
  // 1. Create personal organizations for existing users
  const users = await getAllUsers()
  
  for (const user of users) {
    // Create personal org
    const org = await createOrganization({
      name: `${user.name}'s Workspace`,
      slug: generateSlug(user.name),
      ownerId: user.id,
      plan: 'free',
      type: 'personal'
    })
    
    // Update user metadata
    await updateUserMetadata(user.id, {
      defaultOrganizationId: org.id,
      organizationRoles: [{
        organizationId: org.id,
        roles: ['org:owner']
      }]
    })
  }
  
  // 2. Migrate existing team structures
  // 3. Update permissions and roles
}

Future Enhancements

1. Additional OAuth Providers

  • Microsoft/Azure AD
  • Apple Sign-In
  • LinkedIn
  • Twitter/X

2. Advanced Security Features

  • Multi-factor authentication (MFA)
  • Device fingerprinting
  • Anomaly detection
  • IP allowlisting
  • Session management per organization

3. Enterprise Features

  • SAML SSO integration
  • Active Directory sync
  • Custom domain support per organization
  • Advanced role management with custom permissions
  • Audit logs per organization
  • Compliance reporting (SOC2, HIPAA)

4. Developer Experience

  • Shared authentication package
  • CLI tools for setup
  • Automated testing suite
  • Comprehensive documentation
  • SDK for custom integrations

5. Advanced Organization Features

  • Organization hierarchies (parent/child orgs)
  • Cross-organization resource sharing
  • Organization templates
  • Automated onboarding workflows
  • Custom branding per organization
  • White-label options

On this page