Authentication Architecture Design
Overview
The do.dev platform uses a satellite domain architecture with Clerk.dev for authentication and Convex for data storage. This hybrid approach provides centralized authentication across multiple subdomains 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 "Primary Auth Domain"
AUTH[auth.do.dev<br/>Port 3000]
CLERK_PRIMARY[Clerk Primary Domain]
end
subgraph "Satellite Domains"
DODEV[do.dev<br/>Port 3005<br/>Main Dashboard]
PROMPTNOW[promptnow.do.dev<br/>Port 3001]
GROKTALK[groktalk.do.dev<br/>Port 3002]
BITURL[biturl.do.dev<br/>Port 3003]
OTHER[Other *.do.dev apps<br/>Ports 3004-3010]
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
AUTH --> CLERK_PRIMARY
CLERK_PRIMARY --> CLERK_API
CLERK_API --> OAUTH
DODEV --> AUTH
PROMPTNOW --> AUTH
GROKTALK --> AUTH
BITURL --> AUTH
OTHER --> AUTH
DODEV --> CONVEX
DODEV --> ADMIN
ADMIN --> RBAC
ADMIN --> ORGMGMT
PROMPTNOW --> CONVEX
GROKTALK --> CONVEX
CONVEX --> STORAGE
CONVEX --> ORG
CONVEX --> TEAMS
CONVEX --> RBACCore Components
1. Primary Authentication Domain (auth.do.dev)
Purpose: Central authentication hub for all do.dev applications
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 across domains
Technology Stack:
- Next.js 15.2.3 with App Router
- Clerk.dev (Primary Domain Mode)
- TypeScript 5.7.3
- Tailwind CSS v4
2. Satellite Domains (*.do.dev)
Purpose: Individual applications that rely on auth.do.dev for authentication
Key Features:
- Seamless SSO experience
- Shared session across domains
- Role-based access control with organization context
- Protected routes with automatic redirects
- Local user context from Clerk
- Organization and team awareness
Main Dashboard (do.dev):
- Central hub for all do.dev services
- Organization and team management
- Role and permission administration
- User invitation and onboarding
- Service monitoring and analytics
Configuration:
// Satellite domain configuration
{
isSatellite: true,
domain: "localhost:3000", // Primary auth domain
signInUrl: "http://localhost:3000/sign-in",
signUpUrl: "http://localhost:3000/sign-up",
afterSignInUrl: "http://localhost:3005", // Return URL
afterSignUpUrl: "http://localhost:3005",
// Organization context
organizationId: process.env.NEXT_PUBLIC_ORG_ID,
allowPersonalWorkspace: true
}3. 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 Satellite as Satellite App
participant Auth as auth.do.dev
participant Clerk as Clerk API
participant Convex
User->>Satellite: Access protected route
Satellite->>Auth: Redirect with return URL
Auth->>User: Show sign-in form
User->>Auth: Submit credentials
Auth->>Clerk: Authenticate user
Clerk->>Auth: Return session
Auth->>Satellite: Redirect with session
Satellite->>Clerk: Validate session
Satellite->>Convex: Sync user data
Satellite->>User: Show authenticated content2. Sign-Out Flow
sequenceDiagram
participant User
participant Satellite as Satellite App
participant Auth as auth.do.dev
participant Clerk as Clerk API
User->>Satellite: Click sign out
Satellite->>Auth: Redirect to /api/sign-out
Auth->>Clerk: Revoke session
Auth->>Auth: Clear cookies
Auth->>Satellite: Redirect to origin
Satellite->>User: Show signed out state3. OAuth Flow
sequenceDiagram
participant User
participant Auth as auth.do.dev
participant OAuth as OAuth Provider
participant Clerk as Clerk API
participant Satellite as Satellite App
User->>Auth: Click OAuth sign-in
Auth->>OAuth: Redirect to provider
OAuth->>User: Request authorization
User->>OAuth: Approve access
OAuth->>Auth: Return with code
Auth->>Clerk: Exchange for tokens
Clerk->>Auth: Create session
Auth->>Satellite: Redirect with sessionAPI Design
Authentication API Endpoints
Primary Domain (auth.do.dev)
// Sign-out API
POST /api/sign-out
Query: { return: string } // Return URL
Response: Redirect to return URL
// Onboarding API
POST /api/onboarding
Body: { appId: string }
Response: { success: boolean, user: ClerkUser }
// Webhook Handler
POST /api/webhooks/clerk
Body: ClerkWebhookEvent
Response: { success: boolean }Satellite Domains
// 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}
isSatellite={true}
domain={CLERK_DOMAIN}
signInUrl={CLERK_SIGN_IN_URL}
signUpUrl={CLERK_SIGN_UP_URL}
>
<ConvexProvider client={convexClient}>
<PostAuthHandler>
<ConditionalLayoutWrapper>
{children}
</ConditionalLayoutWrapper>
</PostAuthHandler>
</ConvexProvider>
</ClerkProvider>Security Considerations
1. Session Management
- Sessions are managed centrally by Clerk
- Satellite domains validate sessions on each request
- Session tokens are httpOnly cookies
- Automatic session refresh with token rotation
2. CORS Configuration
- Primary domain allows requests from all satellite domains
- Credentials are included in cross-origin requests
- Strict origin validation for API endpoints
3. Environment Variables
# Primary Domain
CLERK_SECRET_KEY=sk_test_***
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
CLERK_SATELLITE_URLS=http://localhost:3005,...
# Satellite Domain
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
CLERK_SECRET_KEY=sk_test_***
NEXT_PUBLIC_CLERK_IS_SATELLITE=true
NEXT_PUBLIC_CLERK_DOMAIN=localhost:3000
NEXT_PUBLIC_CLERK_SIGN_IN_URL=http://localhost:3000/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=http://localhost:3000/sign-up4. 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)
}Migration Strategy
Phase 1: Infrastructure Setup ✅
- Set up Clerk account and configure domains
- Create auth.do.dev application
- Configure satellite domains
- Implement basic authentication flows
Phase 2: Data Migration (Current)
- Map Convex users to Clerk users
- Migrate user roles and permissions
- Sync profile data
- Maintain backward compatibility
Phase 3: Feature Parity
- Implement all authentication features
- Update all UI components
- Test all user flows
- Performance optimization
Phase 4: Cleanup
- Remove old Convex auth code
- Update documentation
- Create shared auth package
- Deploy to production
Performance Optimizations
1. Caching Strategy
- Cache user data in satellite apps
- 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 cross-domain requests
- Use HTTP/2 for parallel requests
- Implement request batching
Monitoring and Analytics
1. Key Metrics
- Authentication success rate
- Session duration
- Cross-domain redirect time
- API response times
2. Error Tracking
- Failed authentication attempts
- Session validation errors
- API endpoint failures
- CORS 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_RESDefault 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
- 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