Overview
The Twigz API uses Clerk for authentication integrated with Convex for secure, scalable user management. This system supports multiple authentication methods and provides role-based access control for customers, store owners, and administrators.
Authentication Flow
User Registration/Login
Users authenticate through Clerk using email, phone, or social providers
JWT Token Generation
Clerk generates a secure JWT token for the authenticated user
Convex Integration
Token is automatically validated by Convex for API access
Role-based Access
API functions check user roles and permissions automatically
Setup Authentication
Frontend Setup (React)
Next.js App Router
React Component
React Native
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { ConvexProviderWithClerk } from "convex/react-clerk" ;
import { ConvexReactClient } from "convex/react" ;
const convex = new ConvexReactClient ( process . env . NEXT_PUBLIC_CONVEX_URL ! );
export default function RootLayout ({
children ,
} : {
children : React . ReactNode
}) {
return (
< ClerkProvider
publishableKey = {process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}
>
< ConvexProviderWithClerk client = { convex } >
< html lang = "en" >
< body >{ children } </ body >
</ html >
</ ConvexProviderWithClerk >
</ ClerkProvider >
)
}
Backend Setup (Convex)
Convex Configuration
Environment Variables
// convex/auth.config.ts
export default {
providers: [
{
domain: process . env . CLERK_JWT_ISSUER_DOMAIN ! ,
applicationID: "convex" ,
},
]
} ;
User Roles & Permissions
The system supports three main user roles:
Customer Permissions : Place orders, manage cart, view own data
Access : Customer functions, public store data
Store Owner Permissions : Manage store, products, orders, settings
Access : Store functions, own store data, customer orders
Admin Permissions : Platform management, store approval, analytics
Access : All functions, system-wide data
Protected API Calls
Authenticated Queries
Server-side Authentication
import { useQuery , useMutation } from "convex/react" ;
import { api } from "./convex/_generated/api" ;
function CustomerProfile () {
// Automatically authenticated - returns data for current user
const profile = useQuery ( api . customers . account . getCustomerProfile );
if ( profile === undefined ) return < div > Loading ...</ div > ;
if ( profile === null ) return < div > Please complete your profile </ div > ;
return < div > Welcome , {profile. name } !</ div > ;
}
function StoreOwnerDashboard () {
// Only accessible to store owners
const store = useQuery ( api . stores . queries . getStoreByAuthenticatedOwner );
const updateStore = useMutation ( api . stores . operations . updateStore );
const handleStoreUpdate = async ( updates : any ) => {
try {
await updateStore ( updates );
} catch ( error ) {
if ( error . message . includes ( 'permission' )) {
console . error ( 'Access denied: User is not a store owner' );
}
}
};
return < div >{ /* Store dashboard */ } </ div > ;
}
Authentication Patterns
Role-based Component Access
Role Guard Component
Custom Hook
import { useUser } from "@clerk/nextjs" ;
interface RoleGuardProps {
allowedRoles : ( 'customer' | 'store_owner' | 'admin' )[];
children : React . ReactNode ;
fallback ?: React . ReactNode ;
}
function RoleGuard ({ allowedRoles , children , fallback } : RoleGuardProps ) {
const { user } = useUser ();
const userRole = user ?. publicMetadata ?. role as string ;
if ( ! userRole || ! allowedRoles . includes ( userRole as any )) {
return fallback || < div > Access denied </ div > ;
}
return <>{ children } </> ;
}
// Usage
< RoleGuard allowedRoles = { [ 'store_owner' , 'admin' ]}>
<StoreManagementPanel />
</RoleGuard>
<RoleGuard allowedRoles={[ 'admin' ]}>
<AdminDashboard />
</RoleGuard>
User Registration Flow
Customer Registration
Store Owner Registration
import { useUser } from "@clerk/nextjs" ;
import { useMutation } from "convex/react" ;
import { api } from "./convex/_generated/api" ;
function CustomerOnboarding () {
const { user } = useUser ();
const registerCustomer = useMutation ( api . customers . account . registerCustomer );
const handleRegistration = async () => {
try {
const customerId = await registerCustomer ({
preferredLanguage: "en"
});
console . log ( 'Customer registered:' , customerId );
// Update Clerk user metadata
await user ?. update ({
publicMetadata: {
role: 'customer' ,
customerId: customerId
}
});
} catch ( error ) {
console . error ( 'Registration failed:' , error );
}
};
return (
< button onClick = { handleRegistration } >
Complete Registration
</ button >
);
}
Error Handling
Status Code : 401{
"error" : "Authentication required" ,
"message" : "Please sign in to access this resource"
}
Status Code : 403{
"error" : "Insufficient permissions" ,
"requiredRole" : "store_owner" ,
"userRole" : "customer"
}
Status Code : 401{
"error" : "Invalid authentication token" ,
"message" : "Token has expired or is malformed"
}
Status Code : 403{
"error" : "Account suspended" ,
"reason" : "Violation of terms of service" ,
"suspendedUntil" : 1640995200000
}
Security Best Practices
Token Security Never expose secret keys in client-side code. Use environment variables for all sensitive configuration.
Role Validation Always validate user roles on both client and server side for security in depth.
Session Management Implement proper session timeout and refresh mechanisms for long-running applications.
Audit Logging Log all authentication events and permission changes for security monitoring.
All API functions automatically validate authentication and permissions. You don’t need to manually check authentication in most cases.
Use Clerk’s built-in components for authentication UI to ensure security best practices and consistent user experience.