Clerk
Build with Clerk for authentication and user management. Use this skill when the
You are an auth specialist who integrates Clerk into projects. Clerk is a complete authentication and user management platform with pre-built UI components, multi-tenancy via organizations, and deep framework integrations for Next.js, React, and other platforms. ## Key Points - Use middleware for route protection — don't check auth in every page - Sync users to your database via webhooks — Clerk is the auth source, your DB is the app data source - Use organizations for multi-tenant apps — don't build tenancy yourself - Use `<SignedIn>` and `<SignedOut>` for conditional UI — cleaner than manual checks - Use JWT templates to pass Clerk auth to external services (Supabase, Hasura) - Set `afterSignOutUrl` on `<UserButton>` to control redirect behavior - Storing passwords or auth state in your own database — let Clerk handle it - Not setting up webhooks — your database won't know about new users - Checking auth in every component instead of using middleware - Exposing `CLERK_SECRET_KEY` to the client — it's server-only - Not handling the loading state from `useUser()` — `user` is null while loading - Building custom org/team logic when Clerk organizations would work ## Quick Example ```bash npm install @clerk/nextjs ``` ```env 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 auth-services-skills/ClerkFull skill: 276 linesClerk Authentication Integration
You are an auth specialist who integrates Clerk into projects. Clerk is a complete authentication and user management platform with pre-built UI components, multi-tenancy via organizations, and deep framework integrations for Next.js, React, and other platforms.
Core Philosophy
Drop-in UI, full control when needed
Clerk provides pre-built <SignIn>, <SignUp>, and <UserButton> components that
work out of the box. When you need custom flows, the same API powers headless
authentication with full control over every step.
Middleware-first in Next.js
Clerk's Next.js integration uses middleware to protect routes. Define which routes are public and which require auth — the middleware handles the rest. No wrapping components in providers or checking session state manually.
Organizations = multi-tenancy
Clerk's organizations feature gives you team/workspace multi-tenancy built in. Users can belong to multiple orgs, have roles, and switch between them. You don't build this yourself.
Setup
Install (Next.js)
npm install @clerk/nextjs
Environment variables
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
Provider (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 (protect routes)
// 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)(.*)'],
};
Key Techniques
Pre-built components
import { SignIn, SignUp, UserButton, SignedIn, SignedOut } from '@clerk/nextjs';
// Sign-in page
export default function SignInPage() {
return <SignIn />;
}
// Sign-up page
export default function SignUpPage() {
return <SignUp />;
}
// Conditional rendering
function Header() {
return (
<nav>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
<SignedOut>
<a href="/sign-in">Sign in</a>
</SignedOut>
</nav>
);
}
Server-side auth (App Router)
import { auth, currentUser } from '@clerk/nextjs/server';
// Get auth state
export default async function DashboardPage() {
const { userId } = await auth();
if (!userId) redirect('/sign-in');
const user = await currentUser();
return <div>Hello {user?.firstName}</div>;
}
// In API routes
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId } = await auth();
if (!userId) return new Response('Unauthorized', { status: 401 });
const data = await db.query.posts.findMany({
where: eq(posts.authorId, userId),
});
return Response.json(data);
}
Client-side hooks
'use client';
import { useUser, useAuth, useClerk, useSignIn } from '@clerk/nextjs';
function Profile() {
const { user, isLoaded } = useUser();
const { userId, sessionId, getToken } = useAuth();
const { signOut } = useClerk();
if (!isLoaded) return <div>Loading...</div>;
return (
<div>
<p>{user?.emailAddresses[0].emailAddress}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
Organizations (multi-tenancy)
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId, orgId, orgRole } = await auth();
if (!orgId) return new Response('No org selected', { status: 400 });
// Query scoped to organization
const data = await db.query.projects.findMany({
where: eq(projects.orgId, orgId),
});
return Response.json(data);
}
// Client-side org switcher
import { OrganizationSwitcher } from '@clerk/nextjs';
function Nav() {
return <OrganizationSwitcher />;
}
Webhooks (sync users to database)
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import type { WebhookEvent } from '@clerk/nextjs/server';
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!;
const headerPayload = await headers();
const svix_id = headerPayload.get('svix-id')!;
const svix_timestamp = headerPayload.get('svix-timestamp')!;
const svix_signature = 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': svix_id,
'svix-timestamp': svix_timestamp,
'svix-signature': svix_signature,
}) as WebhookEvent;
} catch {
return new Response('Invalid signature', { status: 400 });
}
switch (event.type) {
case 'user.created':
await db.insert(users).values({
id: event.data.id,
email: event.data.email_addresses[0]?.email_address,
name: `${event.data.first_name} ${event.data.last_name}`.trim(),
});
break;
case 'user.updated':
await db.update(users).set({
email: event.data.email_addresses[0]?.email_address,
name: `${event.data.first_name} ${event.data.last_name}`.trim(),
}).where(eq(users.id, event.data.id));
break;
case 'user.deleted':
await db.delete(users).where(eq(users.id, event.data.id!));
break;
}
return new Response('OK', { status: 200 });
}
JWT templates (custom claims)
// Get a custom JWT for external services
const { getToken } = useAuth();
const token = await getToken({ template: 'supabase' });
// Use with Supabase
const supabase = createClient(url, anonKey, {
global: { headers: { Authorization: `Bearer ${token}` } },
});
Best Practices
- Use middleware for route protection — don't check auth in every page
- Sync users to your database via webhooks — Clerk is the auth source, your DB is the app data source
- Use organizations for multi-tenant apps — don't build tenancy yourself
- Use
<SignedIn>and<SignedOut>for conditional UI — cleaner than manual checks - Use JWT templates to pass Clerk auth to external services (Supabase, Hasura)
- Set
afterSignOutUrlon<UserButton>to control redirect behavior
Anti-Patterns
- Storing passwords or auth state in your own database — let Clerk handle it
- Not setting up webhooks — your database won't know about new users
- Checking auth in every component instead of using middleware
- Exposing
CLERK_SECRET_KEYto the client — it's server-only - Not handling the loading state from
useUser()—useris null while loading - Building custom org/team logic when Clerk organizations would work
Install this skill directly: skilldb add auth-services-skills
Related Skills
Auth0
Build with Auth0 for enterprise authentication and identity. Use this skill when
Cognito
Build with Amazon Cognito for authentication and user management. Use this skill
Descope
Integrate Descope to add secure, no-code/low-code authentication and user management to your applications.
Firebase Auth
Build with Firebase Authentication for user sign-in. Use this skill when the
Hanko
Integrate Hanko for modern, passwordless authentication in your web applications.
Kinde
Build with Kinde for authentication and user management. Use this skill when the