Clerk Auth
Clerk authentication service with pre-built UI components, session management, and multi-framework support
You are an expert in Clerk for authentication, including its pre-built UI components, session management, webhook integrations, and multi-framework support (Next.js, React, Remix, Astro, Express). ## Key Points - Use Clerk's pre-built `<SignIn />`, `<SignUp />`, and `<UserButton />` components to save development time; customize with the `appearance` prop or Clerk themes. - Set up webhooks (`user.created`, `user.updated`, `user.deleted`) to keep your database synchronized with Clerk's user records. - Use JWT templates to issue tokens compatible with your backend (Supabase, Convex, Hasura) rather than passing Clerk tokens directly. - Protect routes at the middleware level using `clerkMiddleware` rather than checking auth in every page/route handler. - Use organizations for multi-tenant applications — Clerk handles invitations, roles, and membership out of the box. - **Not syncing users to your database**: Clerk manages user data externally; if your app needs to store user-related data, set up webhooks to create local user records on `user.created`. - **Exposing the secret key**: `CLERK_SECRET_KEY` must never be in client-side code; use `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` for the browser. - **Middleware matcher misconfiguration**: If the middleware matcher is too broad, static assets and Next.js internals get intercepted; use the recommended matcher pattern. - **Webhook verification skipped**: Always verify webhook signatures using the `svix` library; unverified webhooks can be spoofed. - **Token expiry in long-running operations**: Clerk JWTs have a short lifespan (60 seconds by default); always call `getToken()` immediately before each API request rather than caching the token. ## Quick Example ```bash npm install @clerk/nextjs ``` ```typescript // .env.local NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_... NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up ```
skilldb get baas-skills/Clerk AuthFull skill: 342 linesClerk — Authentication as a Service
You are an expert in Clerk for authentication, including its pre-built UI components, session management, webhook integrations, and multi-framework support (Next.js, React, Remix, Astro, Express).
Core Philosophy
Overview
Clerk is a complete user management and authentication platform. It provides drop-in UI components for sign-in, sign-up, and user profiles; supports email/password, magic links, SMS, OAuth, SAML SSO, and passkeys; handles session management with JWTs; offers organization and multi-tenant support; and integrates via SDKs for React, Next.js, Remix, Astro, Express, and more. Clerk is a managed service — there is no self-hosted option.
Setup & Configuration
Next.js Setup
npm install @clerk/nextjs
// .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)',
]);
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
React (Vite) Setup
npm install @clerk/clerk-react
import { ClerkProvider } from '@clerk/clerk-react';
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
function App() {
return (
<ClerkProvider publishableKey={PUBLISHABLE_KEY}>
<MainApp />
</ClerkProvider>
);
}
Core Patterns
Pre-built UI Components
import { SignIn, SignUp, UserButton, UserProfile } from '@clerk/nextjs';
// Sign-in page
// app/sign-in/[[...sign-in]]/page.tsx
export default function SignInPage() {
return (
<div className="flex justify-center py-24">
<SignIn />
</div>
);
}
// Sign-up page
// app/sign-up/[[...sign-up]]/page.tsx
export default function SignUpPage() {
return (
<div className="flex justify-center py-24">
<SignUp />
</div>
);
}
// User button (avatar with dropdown menu)
function Navbar() {
return (
<nav>
<UserButton afterSignOutUrl="/" />
</nav>
);
}
// Full user profile management
function SettingsPage() {
return <UserProfile />;
}
Server-side Auth (Next.js App Router)
// In Server Components
import { currentUser, auth } from '@clerk/nextjs/server';
// Get the full user object
async function ProfilePage() {
const user = await currentUser();
if (!user) return <div>Not signed in</div>;
return <div>Hello {user.firstName}</div>;
}
// Get just the auth state (lighter)
async function DashboardPage() {
const { userId, orgId } = await auth();
if (!userId) redirect('/sign-in');
// userId is guaranteed to be set here
}
// In API routes
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Fetch user-specific data
return NextResponse.json({ userId });
}
Client-side Auth Hooks
import { useUser, useAuth, useSignIn, useClerk } from '@clerk/nextjs';
function Dashboard() {
const { isLoaded, isSignedIn, user } = useUser();
const { getToken, signOut } = useAuth();
if (!isLoaded) return <div>Loading...</div>;
if (!isSignedIn) return <div>Please sign in</div>;
const handleApiCall = async () => {
// Get a JWT for your backend
const token = await getToken({ template: 'supabase' }); // or custom template
await fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
};
return (
<div>
<p>Welcome {user.firstName}</p>
<p>Email: {user.primaryEmailAddress?.emailAddress}</p>
<button onClick={handleApiCall}>Fetch Data</button>
<button onClick={() => signOut()}>Sign Out</button>
</div>
);
}
Organizations (Multi-tenancy)
import {
OrganizationSwitcher,
CreateOrganization,
OrganizationProfile,
} from '@clerk/nextjs';
import { auth } from '@clerk/nextjs/server';
// Organization switcher component
function Header() {
return <OrganizationSwitcher />;
}
// Server-side: get active organization
async function OrgPage() {
const { orgId, orgRole, orgSlug } = await auth();
if (!orgId) return <div>Select an organization</div>;
return <div>Current org: {orgSlug} (role: {orgRole})</div>;
}
Webhooks
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!;
const headerPayload = await headers();
const svixId = headerPayload.get('svix-id');
const svixTimestamp = headerPayload.get('svix-timestamp');
const svixSignature = headerPayload.get('svix-signature');
const payload = await req.json();
const body = JSON.stringify(payload);
const wh = new Webhook(WEBHOOK_SECRET);
let event: WebhookEvent;
try {
event = wh.verify(body, {
'svix-id': svixId!,
'svix-timestamp': svixTimestamp!,
'svix-signature': svixSignature!,
}) as WebhookEvent;
} catch {
return new Response('Verification failed', { status: 400 });
}
switch (event.type) {
case 'user.created':
// Sync user to your database
await db.users.create({
clerkId: event.data.id,
email: event.data.email_addresses[0]?.email_address,
name: `${event.data.first_name} ${event.data.last_name}`,
});
break;
case 'user.updated':
await db.users.update({ clerkId: event.data.id, ... });
break;
case 'user.deleted':
await db.users.delete({ clerkId: event.data.id });
break;
}
return new Response('OK', { status: 200 });
}
Custom JWT Templates (for external services)
Configure in Clerk Dashboard under "JWT Templates":
{
"sub": "{{user.id}}",
"email": "{{user.primary_email_address}}",
"name": "{{user.full_name}}",
"org_id": "{{org.id}}",
"org_role": "{{org_membership.role}}"
}
// Use the template to get a signed token for Supabase, Convex, Hasura, etc.
const token = await getToken({ template: 'supabase' });
Express Backend
import express from 'express';
import { clerkMiddleware, requireAuth, getAuth } from '@clerk/express';
const app = express();
app.use(clerkMiddleware());
// Public route
app.get('/', (req, res) => {
res.json({ message: 'Public endpoint' });
});
// Protected route
app.get('/dashboard', requireAuth(), (req, res) => {
const { userId } = getAuth(req);
res.json({ userId });
});
app.listen(3001);
Best Practices
- Use Clerk's pre-built
<SignIn />,<SignUp />, and<UserButton />components to save development time; customize with theappearanceprop or Clerk themes. - Set up webhooks (
user.created,user.updated,user.deleted) to keep your database synchronized with Clerk's user records. - Use JWT templates to issue tokens compatible with your backend (Supabase, Convex, Hasura) rather than passing Clerk tokens directly.
- Protect routes at the middleware level using
clerkMiddlewarerather than checking auth in every page/route handler. - Use organizations for multi-tenant applications — Clerk handles invitations, roles, and membership out of the box.
Common Pitfalls
- Not syncing users to your database: Clerk manages user data externally; if your app needs to store user-related data, set up webhooks to create local user records on
user.created. - Exposing the secret key:
CLERK_SECRET_KEYmust never be in client-side code; useNEXT_PUBLIC_CLERK_PUBLISHABLE_KEYfor the browser. - Middleware matcher misconfiguration: If the middleware matcher is too broad, static assets and Next.js internals get intercepted; use the recommended matcher pattern.
- Webhook verification skipped: Always verify webhook signatures using the
svixlibrary; unverified webhooks can be spoofed. - Token expiry in long-running operations: Clerk JWTs have a short lifespan (60 seconds by default); always call
getToken()immediately before each API request rather than caching the token.
Anti-Patterns
Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.
Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.
Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add baas-skills
Related Skills
Appwrite
Appwrite self-hosted BaaS with database, auth, storage, and serverless functions
AWS Amplify
AWS Amplify BaaS with AppSync GraphQL, Cognito auth, S3 storage, and Lambda functions
Backendless
Backendless BaaS with real-time database, user authentication, Cloud Code,
Convex
Convex real-time backend with reactive queries, mutations, and serverless functions
Encore
Encore is a backend development platform that automatically provisions, configures, and manages cloud infrastructure based on your Go code. It simplifies building and deploying cloud-native applications by allowing you to focus purely on business logic.
Firebase
Firebase BaaS with Firestore, Authentication, Cloud Functions, and Hosting