To secure Clerk + Next.js integration, you need to: (1) configure middleware with proper route matchers and explicit public routes, (2) use auth() in Server Components and API routes to verify users, (3) always use the verified userId for database operations, (4) add webhook routes to publicRoutes to allow Clerk callbacks, and (5) never trust client-provided user IDs. This blueprint ensures middleware protection and server-side verification work together.
TL;DR
Clerk's middleware handles route protection automatically. Use auth() in Server Components, currentUser() when you need full user data, and configure publicRoutes properly. The userId from auth() is verified server-side-use it for database queries.
Middleware Configuration Clerk Next.js
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher([
'/',
'/about',
'/blog(.*)',
'/api/webhooks(.*)',
])
export default clerkMiddleware((auth, req) => {
if (!isPublicRoute(req)) {
auth().protect()
}
})
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
Protected Server Component
import { auth, currentUser } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function Dashboard() {
const { userId } = auth()
if (!userId) {
redirect('/sign-in')
}
// Use userId for database queries
const posts = await db.posts.findMany({
where: { authorId: userId },
})
const user = await currentUser()
return (
<div>
<h1>Welcome, {user?.firstName}</h1>
{posts.map(post => <div key={post.id}>{post.title}</div>)}
</div>
)
}
Protected API Route
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const { userId } = auth()
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await req.json()
const post = await db.posts.create({
data: {
title: body.title,
authorId: userId, // Verified Clerk user ID
},
})
return NextResponse.json(post)
}
Middleware protects routes, not data. Always verify userId in API routes and Server Components. Use the verified userId for all database operations, never client-provided IDs.
Security Checklist
Pre-Launch Checklist
Middleware configured with correct matchers
Public routes explicitly defined
API routes check auth()
Database queries use verified userId
Webhook routes in publicRoutes
Related Integration Stacks
Auth0 + Next.js Alternative NextAuth + Prisma Self-Hosted OAuth Security Patterns