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
Clerk Integration Modern authentication with social logins, MFA, and organization management
Multi-tenancy Complete data isolation between real estate organizations
Role-based Access Granular permissions for admins, agents, and viewers
API Security JWT tokens with organization context validation
Authentication Flow
Organization Management
Organization Structure
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
// 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 ;
}
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 );
}
Role-Based Access Control
Permission System
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
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
const ADMIN_PERMISSIONS = [
Permission . CONTACTS_VIEW ,
Permission . CONTACTS_CREATE ,
Permission . CONTACTS_EDIT ,
Permission . CONTACTS_DELETE ,
// ... all permissions
];
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
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
];
Read-only access for assistants and support staff:
View contacts and deals
View properties and communications
Limited phone system access
Basic AI features
const VIEWER_PERMISSIONS = [
Permission . CONTACTS_VIEW ,
Permission . DEALS_VIEW ,
Permission . PROPERTIES_VIEW ,
Permission . COMMUNICATIONS_VIEW ,
Permission . AI_USE_VOICE_SEARCH
];
API Authentication
Middleware Implementation
// 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
// 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
-- 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
// 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
// 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
// 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
Secure Sessions JWT tokens with configurable expiration and automatic refresh
Multi-device Support Secure sessions across web, mobile, and Chrome extension
Session Revocation Immediate session termination for security incidents
Activity Monitoring Login tracking and suspicious activity detection
Additional Security Measures
// 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
// 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
Database Design Explore the multi-tenant database schema
API Security Learn about API security patterns and best practices
Real-time Features Understand authenticated real-time communication
Security Note : Never store sensitive authentication data in client-side storage. Always use secure HTTP-only cookies or rely on Clerk’s session management.