Skip to main content

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

1

User Registration/Login

Users authenticate through Clerk using email, phone, or social providers
2

JWT Token Generation

Clerk generates a secure JWT token for the authenticated user
3

Convex Integration

Token is automatically validated by Convex for API access
4

Role-based Access

API functions check user roles and permissions automatically

Setup Authentication

Frontend Setup (React)

// 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/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

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

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

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.