| name | clerk-security-basics |
| description | Implement security best practices with Clerk authentication.
Use when securing your application, reviewing auth implementation,
or hardening Clerk configuration.
Trigger with phrases like "clerk security", "secure clerk",
"clerk best practices", "clerk hardening".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Clerk Security Basics
Overview
Implement security best practices for Clerk authentication in your application.
Prerequisites
- Clerk SDK installed and configured
- Understanding of authentication security concepts
- Production deployment planned or active
Instructions
Step 1: Secure Environment Variables
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
.env.local
.env.production
.env*.local
const requiredEnvVars = [
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
'CLERK_SECRET_KEY'
]
export function validateEnv() {
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`)
}
}
const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!
if (!pk.startsWith('pk_test_') && !pk.startsWith('pk_live_')) {
throw new Error('Invalid publishable key format')
}
}
Step 2: Secure Middleware Configuration
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)'
])
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
const isSensitiveRoute = createRouteMatcher(['/api/admin(.*)', '/api/billing(.*)'])
export default clerkMiddleware(async (auth, request) => {
const { userId, orgRole } = await auth()
const response = NextResponse.next()
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
if (!isPublicRoute(request)) {
if (!userId) {
.( (, request.))
}
}
((request) && orgRole !== ) {
.( (, request.))
}
((request)) {
.(, {
: request..,
userId,
: ().()
})
}
response
})
Step 3: Secure API Routes
import { auth } from '@clerk/nextjs/server'
import { headers } from 'next/headers'
export async function POST(request: Request) {
const { userId, sessionId } = await auth()
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const headersList = await headers()
const origin = headersList.get('origin')
const allowedOrigins = [
process.env.NEXT_PUBLIC_APP_URL,
'https://yourdomain.com'
]
if (origin && !allowedOrigins.includes(origin)) {
return Response.json({ error: 'Invalid origin' }, { status: 403 })
}
const contentType = headersList.get()
(!contentType?.()) {
.({ : }, { : })
}
body
{
body = request.()
} {
.({ : }, { : })
}
.({ : })
}
Step 4: Secure Webhook Handling
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
if (!WEBHOOK_SECRET) {
console.error('CLERK_WEBHOOK_SECRET not configured')
return Response.json({ error: 'Configuration error' }, { status: 500 })
}
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')
if (!svix_id || !svix_timestamp || !svix_signature) {
.({ : }, { : })
}
body = req.()
wh = ()
:
{
evt = wh.(body, {
: svix_id,
: svix_timestamp,
: svix_signature
})
} (err) {
.(, err)
.({ : }, { : })
}
eventType = evt.
processed = (svix_id)
(processed) {
.({ : })
}
(evt)
(svix_id)
.({ : })
}
Step 5: Session Security
import { auth } from '@clerk/nextjs/server'
export async function validateSession() {
const { userId, sessionClaims } = await auth()
if (!userId) {
throw new Error('No session')
}
const issuedAt = sessionClaims?.iat
const maxAge = 60 * 60
if (issuedAt && Date.now() / 1000 - issuedAt > maxAge) {
throw new Error('Session too old, please re-authenticate')
}
return { userId, sessionClaims }
}
export async function requireFreshAuth() {
const { userId, sessionClaims } = await auth()
if (!userId) {
throw new Error('Not authenticated')
}
const issuedAt = sessionClaims?.iat
const freshThreshold = *
(issuedAt && .() / - issuedAt > freshThreshold) {
()
}
{ userId }
}
Output
- Secure environment configuration
- Hardened middleware
- Protected API routes
- Verified webhook handling
Security Checklist
Resources
Next Steps
Proceed to clerk-prod-checklist for production readiness.