| name | clerk-core-workflow-b |
| description | Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh logic.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Clerk Core Workflow B: Session & Middleware
Overview
Manage user sessions, protect routes with middleware, and handle JWT tokens.
Prerequisites
- Clerk SDK installed and configured
- Authentication flows implemented
- Understanding of Next.js middleware
Instructions
Step 1: Advanced Middleware Configuration
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)',
'/api/public(.*)'
])
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
const isAPIRoute = createRouteMatcher(['/api/(.*)'])
export default clerkMiddleware(async (auth, request) => {
const { userId, orgRole, sessionClaims } = await auth()
if (isPublicRoute(request)) {
return NextResponse.next()
}
if (!userId) {
const signInUrl = new URL('/sign-in', request.url)
signInUrl.searchParams.set('redirect_url', request.url)
return NextResponse.redirect(signInUrl)
}
if (isAdminRoute(request)) {
if (orgRole !== 'org:admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url))
}
}
if (isAPIRoute(request)) {
const response = NextResponse.next()
response.headers.set('x-user-id', userId)
return response
}
return NextResponse.next()
})
export const config = {
matcher: ['/((?!_next|[^?]*\\.(?:html?|css|js|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)', '/']
}
Step 2: Session Management
'use client'
import { useSession, useAuth } from '@clerk/nextjs'
export function SessionManager() {
const { session, isLoaded } = useSession()
const { signOut } = useAuth()
if (!isLoaded) return <div>Loading session...</div>
if (!session) return <div>No active session</div>
const handleSignOutAll = async () => {
await signOut({ sessionId: 'all' })
}
const handleSignOutCurrent = async () => {
await signOut()
}
return (
<div>
<h2>Session Info</h2>
<p>Session ID: {session.id}</p>
<p>Created: {new Date(session.createdAt).toLocaleString()}
Last Active: {new Date(session.lastActiveAt).toLocaleString()}
Expires: {new Date(session.expireAt).toLocaleString()}
Sign Out
Sign Out All Devices
)
}
Step 3: Token Management
'use client'
import { useAuth } from '@clerk/nextjs'
export function useClerkToken() {
const { getToken, isLoaded, isSignedIn } = useAuth()
const fetchWithAuth = async (url: string, options: RequestInit = {}) => {
if (!isLoaded || !isSignedIn) {
throw new Error('Not authenticated')
}
const token = await getToken()
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
}
const fetchWithCustomTemplate = async (url: string, template: string) => {
const token = await getToken({ template })
return fetch(url, {
headers: {
Authorization:
}
})
}
{ fetchWithAuth, fetchWithCustomTemplate, getToken }
}
Step 4: Server-Side Session Validation
import { auth } from '@clerk/nextjs/server'
import { headers } from 'next/headers'
export async function GET() {
const { userId, sessionId, sessionClaims } = await auth()
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const email = sessionClaims?.email as string
const role = sessionClaims?.metadata?.role as string
const sessionAge = Date.now() - (sessionClaims?.iat ?? 0) * 1000
const maxAge = 60 * 60 * 1000
if (sessionAge > maxAge) {
return Response.json({ error: 'Session expired' }, { status: })
}
.({
userId,
sessionId,
email,
role
})
}
Step 5: Multi-Session Support
'use client'
import { useSessionList, useSession } from '@clerk/nextjs'
export function SessionList() {
const { sessions, isLoaded, setActive } = useSessionList()
const { session: currentSession } = useSession()
if (!isLoaded) return <div>Loading sessions...</div>
return (
<div>
<h2>Active Sessions</h2>
<ul>
{sessions?.map((session) => (
<li key={session.id}>
<span>{session.id}</span>
<span>{session.id === currentSession?.id ? ' (current)' : ''}</span>
<button onClick={() => setActive({ session: session.id })}>
Switch
</button>
<button onClick={() => session.remove()}>
Revoke
</button>
</>
))}
)
}
Output
- Protected routes with middleware
- Session management UI
- Token refresh handling
- Multi-session support
Error Handling
| Error | Cause | Solution |
|---|
| Session not found | Expired or revoked | Redirect to sign-in |
| Token expired | JWT lifetime exceeded | Call getToken() for fresh token |
| Middleware loop | Incorrect matcher | Check matcher regex excludes static files |
| Headers already sent | Response already started | Check middleware order |
Examples
Rate-Limited Middleware
import { clerkMiddleware } from '@clerk/nextjs/server'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s')
})
export default clerkMiddleware(async (auth, request) => {
const { userId } = await auth()
if (userId) {
const { success } = await ratelimit.limit(userId)
if (!success) {
return Response.json({ error: 'Rate limited' }, { status: 429 })
}
}
})
Resources
Next Steps
Proceed to clerk-common-errors for troubleshooting common issues.