How to Set Up Clerk Authentication in 5 Minutes
How to Set Up Clerk Authentication in 5 Minutes
Clerk gives you a complete auth system with pre-built UI components. No building login forms, no managing sessions, no handling password resets. This guide gets you from zero to authenticated in under 5 minutes.
What You'll Build
- Sign-in and sign-up flows (email, Google, GitHub)
- Protected routes with middleware
- User profile management
- Organization/team support
- Role-based access control
Prerequisites: Next.js 14+, Clerk account (free: 10,000 MAU).
1. Setup (2 minutes)
Install
npm install @clerk/nextjs
Environment Variables
Create a Clerk application at dashboard.clerk.com, then copy your keys:
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
Add Provider
// 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>
);
}
Add Middleware
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/pricing(.*)',
'/blog(.*)',
'/api/webhooks(.*)',
]);
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
That's it. Authentication is now active. Users hitting any non-public route will be redirected to sign in.
2. Auth Components (1 minute)
Sign In / Sign Up Buttons
// components/Header.tsx
import {
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs';
export function Header() {
return (
<header>
<nav>
<SignedOut>
<SignInButton mode="modal" />
<SignUpButton mode="modal" />
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</nav>
</header>
);
}
The UserButton renders a dropdown with profile, settings, and sign out — no code needed.
Custom Sign-In Page (Optional)
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs';
export default function SignInPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignIn
appearance={{
elements: {
rootBox: 'mx-auto',
card: 'shadow-xl',
},
}}
/>
</div>
);
}
Add to env:
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
3. Access User Data
Client-Side
'use client';
import { useUser } from '@clerk/nextjs';
export function Dashboard() {
const { user, isLoaded } = useUser();
if (!isLoaded) return <div>Loading...</div>;
return (
<div>
<h1>Welcome, {user?.firstName}!</h1>
<p>Email: {user?.primaryEmailAddress?.emailAddress}</p>
<img src={user?.imageUrl} alt="Profile" width={64} />
</div>
);
}
Server-Side
// app/dashboard/page.tsx
import { currentUser } from '@clerk/nextjs/server';
export default async function DashboardPage() {
const user = await currentUser();
if (!user) return <div>Not signed in</div>;
return (
<div>
<h1>Dashboard</h1>
<p>User ID: {user.id}</p>
<p>Email: {user.emailAddresses[0]?.emailAddress}</p>
</div>
);
}
In API Routes
// app/api/user/route.ts
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
const data = await getUserData(userId);
return NextResponse.json(data);
}
4. Social Login
Enable Providers
In Clerk Dashboard → User & Authentication → Social Connections:
- Toggle on: Google, GitHub, Apple, etc.
- Add OAuth credentials for each provider
They appear automatically in your sign-in/sign-up components. No code changes needed.
5. Organizations
Enable Organizations
In Clerk Dashboard → Organizations → Enable.
Organization Switcher
import { OrganizationSwitcher } from '@clerk/nextjs';
export function OrgSwitcher() {
return (
<OrganizationSwitcher
afterCreateOrganizationUrl="/dashboard"
afterSelectOrganizationUrl="/dashboard"
/>
);
}
Check Organization in API
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId, orgId, orgRole } = await auth();
if (!orgId) {
return NextResponse.json({ error: 'No organization selected' }, { status: 400 });
}
// orgRole: 'org:admin' | 'org:member'
if (orgRole !== 'org:admin') {
return NextResponse.json({ error: 'Admin only' }, { status: 403 });
}
return NextResponse.json({ orgId, role: orgRole });
}
6. Webhooks
Sync Clerk events with your database:
Set Up Webhook
In Clerk Dashboard → Webhooks → Add Endpoint:
- URL:
https://your-app.com/api/webhooks/clerk - Events:
user.created,user.updated,user.deleted
Handle Events
// 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 body = await req.text();
const headerPayload = headers();
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
let evt: WebhookEvent;
try {
evt = wh.verify(body, {
'svix-id': headerPayload.get('svix-id')!,
'svix-timestamp': headerPayload.get('svix-timestamp')!,
'svix-signature': headerPayload.get('svix-signature')!,
}) as WebhookEvent;
} catch {
return new Response('Invalid signature', { status: 400 });
}
switch (evt.type) {
case 'user.created':
await createUserInDB({
clerkId: evt.data.id,
email: evt.data.email_addresses[0]?.email_address,
name: `${evt.data.first_name} ${evt.data.last_name}`,
});
break;
case 'user.updated':
await updateUserInDB(evt.data.id, evt.data);
break;
case 'user.deleted':
await deleteUserFromDB(evt.data.id!);
break;
}
return new Response('OK');
}
7. Theming
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import { dark } from '@clerk/themes';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider
appearance={{
baseTheme: dark,
variables: {
colorPrimary: '#2563eb',
borderRadius: '0.5rem',
},
elements: {
card: 'shadow-lg',
formButtonPrimary: 'bg-blue-600 hover:bg-blue-700',
},
}}
>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
Pricing
| Plan | MAU | Price |
|---|---|---|
| Free | 10,000 | $0 |
| Pro | 10,000+ | $25/month + $0.02/MAU over 10K |
| Enterprise | Custom | Custom |
Free tier includes: unlimited social logins, organizations, custom domains.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Forgetting middleware | No routes are protected | Always add middleware.ts |
| Not syncing with database | Can't query users in your DB | Set up webhooks for user events |
| Client-side only auth checks | Security bypass | Use auth() server-side for sensitive routes |
| Not handling org context | Multi-tenant bugs | Check orgId in org-scoped routes |
| Exposing secret key | Account compromise | Only NEXT_PUBLIC_ key goes to client |
Choosing auth? Compare Clerk vs Auth0 vs Firebase Auth on APIScout — pricing, DX, and feature comparison.