--- name: clerk-reference-architecture description: 'Reference architecture patterns for Clerk authentication. Use when designing application architecture, planning auth flows, or implementing enterprise-grade authentication. Trigger with phrases like "clerk architecture", "clerk design", "clerk system design", "clerk integration patterns". ' allowed-tools: Read, Write, Edit, Grep version: 1.13.0 license: MIT author: Jeremy Longshore tags: - saas - clerk - authentication compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # Clerk Reference Architecture ## Overview Reference architectures for implementing Clerk in common application patterns: Next.js full-stack, microservices with shared auth, multi-tenant SaaS, and mobile + web with shared backend. ## Prerequisites - Understanding of web application architecture - Familiarity with authentication patterns (JWT, sessions, OAuth) - Knowledge of your tech stack and scaling requirements ## Instructions ### Architecture 1: Next.js Full-Stack Application ``` Browser │ ├─▸ Next.js Middleware (clerkMiddleware) │ └─▸ Validates session token on every request │ ├─▸ Server Components (auth(), currentUser()) │ └─▸ Direct access to user data, no network call │ ├─▸ Client Components (useUser(), useAuth()) │ └─▸ Real-time auth state via ClerkProvider │ ├─▸ API Routes (auth() for userId, getToken() for JWT) │ └─▸ Call external services with Clerk JWT │ └─▸ Webhooks (/api/webhooks/clerk) └─▸ Sync user data to database ``` ```typescript // app/layout.tsx — entry point import { ClerkProvider } from '@clerk/nextjs' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ```typescript // middleware.ts — auth boundary import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' const isPublic = createRouteMatcher(['/', '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)']) export default clerkMiddleware(async (auth, req) => { if (!isPublic(req)) await auth.protect() }) ``` ### Architecture 2: Microservices with Shared Auth ``` Browser ─▸ API Gateway / BFF (Next.js + Clerk) │ ├─▸ Service A (Node.js) ──── verifies JWT ├─▸ Service B (Python) ──── verifies JWT └─▸ Service C (Go) ──────── verifies JWT ``` ```typescript // BFF: Generate service-specific JWT // app/api/proxy/[service]/route.ts import { auth } from '@clerk/nextjs/server' export async function GET(req: Request, { params }: { params: { service: string } }) { const { userId, getToken } = await auth() if (!userId) return Response.json({ error: 'Unauthorized' }, { status: 401 }) // Get JWT with service-specific claims const token = await getToken({ template: params.service }) const serviceUrls: Record = { billing: process.env.BILLING_SERVICE_URL!, analytics: process.env.ANALYTICS_SERVICE_URL!, notifications: process.env.NOTIFICATION_SERVICE_URL!, } const response = await fetch(`${serviceUrls[params.service]}/api/data`, { headers: { Authorization: `Bearer ${token}` }, }) return Response.json(await response.json()) } ``` ```typescript // Downstream service: Verify Clerk JWT // services/billing/src/middleware.ts (Express) import { clerkMiddleware, requireAuth } from '@clerk/express' app.use(clerkMiddleware()) app.get('/api/data', requireAuth(), (req, res) => { // req.auth.userId is available res.json({ userId: req.auth.userId }) }) ``` ### Architecture 3: Multi-Tenant SaaS ``` Tenant A (org_abc) ──┐ Tenant B (org_def) ──┤──▸ Shared App ──▸ Shared DB (tenant-scoped queries) Tenant C (org_ghi) ──┘ ``` ```typescript // lib/tenant.ts — tenant-scoped data access import { auth } from '@clerk/nextjs/server' export async function getTenantData(query: (orgId: string) => Promise): Promise { const { orgId } = await auth() if (!orgId) throw new Error('No organization selected') return query(orgId) } // Usage: export async function getProjects() { return getTenantData((orgId) => db.project.findMany({ where: { organizationId: orgId } }) ) } ``` ```typescript // middleware.ts — enforce org context on tenant routes import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' const isTenantRoute = createRouteMatcher(['/app(.*)']) export default clerkMiddleware(async (auth, req) => { if (isTenantRoute(req)) { const { orgId } = await auth.protect() if (!orgId) { // Redirect to org selector if no org is active return Response.redirect(new URL('/select-org', req.url)) } } }) ``` ```typescript // app/select-org/page.tsx import { OrganizationSwitcher } from '@clerk/nextjs' export default function SelectOrg() { return (

Select Your Organization

) } ``` ### Architecture 4: Mobile + Web with Shared Backend ``` Web App (Next.js + @clerk/nextjs) ──┐ Mobile App (React Native + @clerk/clerk-expo) ──┤──▸ Backend API (Express + @clerk/express) └──▸ Database ``` ```typescript // Backend API: Express with Clerk // server.ts import express from 'express' import { clerkMiddleware, requireAuth, getAuth } from '@clerk/express' const app = express() // Apply Clerk middleware globally app.use(clerkMiddleware()) // Public endpoint app.get('/api/public', (req, res) => { res.json({ message: 'Public endpoint' }) }) // Protected endpoint (works with both web and mobile clients) app.get('/api/profile', requireAuth(), async (req, res) => { const { userId } = getAuth(req) const user = await db.user.findUnique({ where: { clerkId: userId } }) res.json({ user }) }) app.listen(3001) ``` ## Output - Next.js full-stack architecture with middleware, server/client components, and webhooks - Microservices architecture with BFF proxy and JWT-based service auth - Multi-tenant SaaS with organization-scoped data access - Mobile + web with shared Express backend using `@clerk/express` ## Error Handling | Pattern | Common Issue | Solution | |---------|-------------|----------| | Full-stack | Middleware redirect loop | Add sign-in route to public routes | | Microservices | JWT template not configured | Create JWT template in Dashboard per service | | Multi-tenant | No org selected | Redirect to org selector before tenant routes | | Mobile + Web | Token not sent from mobile | Include `Authorization: Bearer ` in mobile fetch | ## Examples ### Database Schema for Clerk Integration ```prisma // prisma/schema.prisma model User { id String @id @default(cuid()) clerkId String @unique email String @unique name String? createdAt DateTime @default(now()) posts Post[] orgMemberships OrgMembership[] } model OrgMembership { id String @id @default(cuid()) userId String orgId String // Clerk organization ID role String // org:admin, org:member, etc. user User @relation(fields: [userId], references: [id]) @@unique([userId, orgId]) } ``` ## Resources - [Clerk Architecture Patterns](https://clerk.com/docs/quickstarts/nextjs) - [Clerk Organizations (Multi-Tenant)](https://clerk.com/docs/organizations/overview) - [Clerk Express Integration](https://clerk.com/docs/quickstarts/express) ## Next Steps Proceed to `clerk-multi-env-setup` for multi-environment configuration.