> ## Documentation Index
> Fetch the complete documentation index at: https://docs.winnerr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication System

> Multi-tenant authentication architecture with Clerk, organization management, and security patterns

Winnerr's authentication system is built on Clerk with comprehensive multi-tenant organization support, providing secure access control for real estate brokerages of all sizes. The system ensures complete data isolation between organizations while maintaining seamless user experiences.

## Authentication Architecture

### Core Components

<CardGroup cols={2}>
  <Card title="Clerk Integration" icon="key">
    Modern authentication with social logins, MFA, and organization management
  </Card>

  <Card title="Multi-tenancy" icon="building">
    Complete data isolation between real estate organizations
  </Card>

  <Card title="Role-based Access" icon="users">
    Granular permissions for admins, agents, and viewers
  </Card>

  <Card title="API Security" icon="shield">
    JWT tokens with organization context validation
  </Card>
</CardGroup>

## Authentication Flow

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant A as App
    participant C as Clerk
    participant API as API Server
    participant DB as Database
    
    U->>A: Login Request
    A->>C: Redirect to Clerk
    C->>C: Authenticate User
    C->>A: Redirect with Session
    A->>C: Get User + Organizations
    C->>A: User Data + Organization List
    A->>A: Select Active Organization
    A->>API: API Request with JWT + Org Context
    API->>C: Verify JWT Token
    C->>API: Validated User + Organization
    API->>DB: Query with Organization Filter
    DB->>API: Organization-scoped Data
    API->>A: Response
    A->>U: Display Dashboard
```

## Organization Management

### Organization Structure

```typescript theme={null}
interface Organization {
  id: string;
  name: string;
  slug: string;
  logo?: string;
  settings: OrganizationSettings;
  members: OrganizationMembership[];
  createdAt: Date;
  updatedAt: Date;
}

interface OrganizationMembership {
  id: string;
  userId: string;
  organizationId: string;
  role: 'ADMIN' | 'AGENT' | 'VIEWER';
  permissions: Permission[];
  joinedAt: Date;
}
```

### Organization Creation Flow

<AccordionGroup>
  <Accordion icon="plus" title="New Organization Setup">
    ```typescript theme={null}
    // Organization creation process
    export async function createOrganization(data: CreateOrgData) {
      // 1. Create Clerk organization
      const clerkOrg = await clerkClient.organizations.createOrganization({
        name: data.name,
        slug: data.slug,
        createdBy: data.userId
      });
      
      // 2. Create database record
      const organization = await database.organization.create({
        data: {
          id: clerkOrg.id,
          name: data.name,
          slug: data.slug,
          settings: {
            create: {
              timezone: data.timezone,
              businessHours: data.businessHours
            }
          }
        }
      });
      
      // 3. Set up initial permissions
      await setupDefaultPermissions(organization.id, data.userId);
      
      // 4. Initialize Twilio sub-account
      await createTwilioSubAccount(organization.id);
      
      return organization;
    }
    ```
  </Accordion>

  <Accordion icon="user-plus" title="Member Invitation">
    ```typescript theme={null}
    export async function inviteMember(
      orgId: string, 
      email: string, 
      role: OrganizationRole
    ) {
      // 1. Send Clerk invitation
      const invitation = await clerkClient.organizations.createOrganizationInvitation({
        organizationId: orgId,
        emailAddress: email,
        role: role.toLowerCase() as ClerkRole
      });
      
      // 2. Create pending membership
      await database.organizationMembership.create({
        data: {
          organizationId: orgId,
          invitationId: invitation.id,
          role,
          status: 'PENDING'
        }
      });
      
      // 3. Send welcome email
      await sendInvitationEmail(email, orgId, role);
    }
    ```
  </Accordion>
</AccordionGroup>

## Role-Based Access Control

### Permission System

```typescript theme={null}
enum Permission {
  // Contact Management
  CONTACTS_VIEW = 'contacts:view',
  CONTACTS_CREATE = 'contacts:create',
  CONTACTS_EDIT = 'contacts:edit',
  CONTACTS_DELETE = 'contacts:delete',
  
  // Deal Management
  DEALS_VIEW = 'deals:view',
  DEALS_CREATE = 'deals:create',
  DEALS_EDIT = 'deals:edit',
  DEALS_DELETE = 'deals:delete',
  DEALS_VIEW_COMMISSIONS = 'deals:view_commissions',
  
  // Property Management
  PROPERTIES_VIEW = 'properties:view',
  PROPERTIES_CREATE = 'properties:create',
  PROPERTIES_EDIT = 'properties:edit',
  PROPERTIES_DELETE = 'properties:delete',
  
  // Communication
  COMMUNICATIONS_VIEW = 'communications:view',
  COMMUNICATIONS_SEND = 'communications:send',
  COMMUNICATIONS_EXPORT = 'communications:export',
  
  // Organization Management
  ORG_VIEW_MEMBERS = 'org:view_members',
  ORG_INVITE_MEMBERS = 'org:invite_members',
  ORG_REMOVE_MEMBERS = 'org:remove_members',
  ORG_EDIT_SETTINGS = 'org:edit_settings',
  ORG_VIEW_BILLING = 'org:view_billing',
  
  // Phone System
  PHONE_MAKE_CALLS = 'phone:make_calls',
  PHONE_VIEW_RECORDINGS = 'phone:view_recordings',
  PHONE_MANAGE_NUMBERS = 'phone:manage_numbers',
  
  // AI Features
  AI_USE_VOICE_SEARCH = 'ai:use_voice_search',
  AI_VIEW_SENTIMENT = 'ai:view_sentiment',
  AI_EXPORT_INSIGHTS = 'ai:export_insights'
}
```

### Role Definitions

<AccordionGroup>
  <Accordion icon="crown" title="Admin Role">
    **Full access to all organization features:**

    * All contact, deal, and property management
    * Organization settings and member management
    * Billing and subscription management
    * Phone system configuration
    * AI features and analytics
    * Data export and reporting

    ```typescript theme={null}
    const ADMIN_PERMISSIONS = [
      Permission.CONTACTS_VIEW,
      Permission.CONTACTS_CREATE,
      Permission.CONTACTS_EDIT,
      Permission.CONTACTS_DELETE,
      // ... all permissions
    ];
    ```
  </Accordion>

  <Accordion icon="user-tie" title="Agent Role">
    **Access to CRM features with some restrictions:**

    * Full contact and deal management
    * Property viewing and creation
    * Communication features
    * Phone system usage
    * AI features for productivity
    * Limited analytics access

    ```typescript theme={null}
    const AGENT_PERMISSIONS = [
      Permission.CONTACTS_VIEW,
      Permission.CONTACTS_CREATE,
      Permission.CONTACTS_EDIT,
      Permission.DEALS_VIEW,
      Permission.DEALS_CREATE,
      Permission.DEALS_EDIT,
      Permission.COMMUNICATIONS_VIEW,
      Permission.COMMUNICATIONS_SEND,
      Permission.PHONE_MAKE_CALLS,
      Permission.AI_USE_VOICE_SEARCH,
      // ... agent-specific permissions
    ];
    ```
  </Accordion>

  <Accordion icon="eye" title="Viewer Role">
    **Read-only access for assistants and support staff:**

    * View contacts and deals
    * View properties and communications
    * Limited phone system access
    * Basic AI features

    ```typescript theme={null}
    const VIEWER_PERMISSIONS = [
      Permission.CONTACTS_VIEW,
      Permission.DEALS_VIEW,
      Permission.PROPERTIES_VIEW,
      Permission.COMMUNICATIONS_VIEW,
      Permission.AI_USE_VOICE_SEARCH
    ];
    ```
  </Accordion>
</AccordionGroup>

## API Authentication

### Middleware Implementation

```typescript theme={null}
// apps/api/middleware.ts
import { auth } from '@repo/auth/server';
import { NextRequest, NextResponse } from 'next/server';

export async function middleware(request: NextRequest) {
  // Skip auth for public routes
  if (isPublicRoute(request.nextUrl.pathname)) {
    return NextResponse.next();
  }
  
  try {
    // Verify authentication
    const { userId, orgId } = await auth();
    
    if (!userId) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
    
    // Add context to request headers
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', userId);
    if (orgId) {
      requestHeaders.set('x-organization-id', orgId);
    }
    
    return NextResponse.next({
      request: {
        headers: requestHeaders
      }
    });
  } catch (error) {
    return NextResponse.json(
      { error: 'Authentication failed' },
      { status: 401 }
    );
  }
}
```

### API Route Protection

```typescript theme={null}
// Standard authenticated API route
export async function POST(req: Request) {
  try {
    // 1. Verify authentication and get context
    const { userId, orgId } = await auth();
    
    if (!userId || !orgId) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
    
    // 2. Parse and validate input
    const data = await req.json();
    const validatedData = createPersonSchema.parse(data);
    
    // 3. Check permissions
    await requirePermission(userId, orgId, Permission.CONTACTS_CREATE);
    
    // 4. Execute business logic with organization context
    const person = await database.person.create({
      data: {
        ...validatedData,
        organizationId: orgId, // Enforce organization isolation
        createdBy: userId
      }
    });
    
    // 5. Return response
    return NextResponse.json(person);
  } catch (error) {
    if (error instanceof PermissionError) {
      return NextResponse.json(
        { error: 'Insufficient permissions' },
        { status: 403 }
      );
    }
    
    log.error('API error', { error: parseError(error) });
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}
```

## Multi-tenant Data Isolation

### Database-Level Isolation

```sql theme={null}
-- Row-level security policies ensure organization isolation
CREATE POLICY person_organization_isolation ON "Person"
  FOR ALL USING (
    "organizationId" = current_setting('app.current_organization')::text
  );

CREATE POLICY deal_organization_isolation ON "Deal"
  FOR ALL USING (
    "organizationId" = current_setting('app.current_organization')::text
  );
```

### Application-Level Enforcement

```typescript theme={null}
// Database utility with automatic organization filtering
export class OrganizationDatabase {
  constructor(private orgId: string) {}
  
  get person() {
    return {
      findMany: (args?: Prisma.PersonFindManyArgs) => {
        return database.person.findMany({
          ...args,
          where: {
            ...args?.where,
            organizationId: this.orgId // Automatically enforce isolation
          }
        });
      },
      
      create: (args: Prisma.PersonCreateArgs) => {
        return database.person.create({
          ...args,
          data: {
            ...args.data,
            organizationId: this.orgId // Automatically set organization
          }
        });
      }
      // ... other methods
    };
  }
}

// Usage in API routes
export async function GET(req: Request) {
  const { userId, orgId } = await auth();
  const db = new OrganizationDatabase(orgId);
  
  const people = await db.person.findMany({
    include: { deals: true }
  });
  
  return NextResponse.json(people);
}
```

## Client-Side Authentication

### React Components

```tsx theme={null}
// Auth-aware component
import { useAuth } from '@repo/auth';

export function DashboardPage() {
  const { user, organization, isLoaded } = useAuth();
  
  if (!isLoaded) return <LoadingSpinner />;
  if (!user) return <SignInRedirect />;
  if (!organization) return <OrganizationSetup />;
  
  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>Organization: {organization.name}</p>
      <DashboardContent />
    </div>
  );
}
```

### API Calls with Authentication

```typescript theme={null}
// Auth-aware fetch utility
import { useAuth } from '@repo/auth';

export function useAuthenticatedFetch() {
  const { getToken } = useAuth();
  
  return useCallback(async (url: string, options: RequestInit = {}) => {
    const token = await getToken();
    
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    });
  }, [getToken]);
}

// Usage in components
export function ContactsList() {
  const authFetch = useAuthenticatedFetch();
  
  const { data: contacts } = useSWR('/api/people', authFetch);
  
  return (
    <div>
      {contacts?.map(contact => (
        <ContactCard key={contact.id} contact={contact} />
      ))}
    </div>
  );
}
```

## Security Features

### Session Management

<CardGroup cols={2}>
  <Card title="Secure Sessions" icon="clock">
    JWT tokens with configurable expiration and automatic refresh
  </Card>

  <Card title="Multi-device Support" icon="devices">
    Secure sessions across web, mobile, and Chrome extension
  </Card>

  <Card title="Session Revocation" icon="ban">
    Immediate session termination for security incidents
  </Card>

  <Card title="Activity Monitoring" icon="chart-line">
    Login tracking and suspicious activity detection
  </Card>
</CardGroup>

### Additional Security Measures

```typescript theme={null}
// Rate limiting for authentication endpoints
export const authRateLimit = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts per window
  message: 'Too many authentication attempts',
  standardHeaders: true,
  legacyHeaders: false
});

// IP allowlist for sensitive operations
export function requireIPAllowlist(allowedIPs: string[]) {
  return (req: Request) => {
    const clientIP = getClientIP(req);
    if (!allowedIPs.includes(clientIP)) {
      throw new SecurityError('IP not allowed');
    }
  };
}

// Device fingerprinting for fraud detection
export function trackDeviceFingerprint(req: Request) {
  const fingerprint = generateFingerprint({
    userAgent: req.headers.get('user-agent'),
    acceptLanguage: req.headers.get('accept-language'),
    // ... other headers
  });
  
  return fingerprint;
}
```

## Organization Switching

### Seamless Organization Context

```typescript theme={null}
// Organization context provider
export function OrganizationProvider({ children }: { children: React.ReactNode }) {
  const { user, organization } = useAuth();
  const [activeOrg, setActiveOrg] = useState(organization);
  
  const switchOrganization = useCallback(async (orgId: string) => {
    // Update Clerk session
    await setActiveOrganization({ organizationId: orgId });
    
    // Update local state
    setActiveOrg(orgId);
    
    // Refresh application data
    await revalidateOrganizationData();
  }, []);
  
  return (
    <OrganizationContext.Provider value={{
      activeOrganization: activeOrg,
      switchOrganization
    }}>
      {children}
    </OrganizationContext.Provider>
  );
}
```

## Next Steps

<CardGroup cols={3}>
  <Card title="Database Design" icon="database" href="/architecture/database-design">
    Explore the multi-tenant database schema
  </Card>

  <Card title="API Security" icon="shield" href="/architecture/api-architecture">
    Learn about API security patterns and best practices
  </Card>

  <Card title="Real-time Features" icon="bolt" href="/architecture/real-time-features">
    Understand authenticated real-time communication
  </Card>
</CardGroup>

***

<Warning>
  **Security Note**: Never store sensitive authentication data in client-side storage. Always use secure HTTP-only cookies or rely on Clerk's session management.
</Warning>
