144 lines
3.6 KiB
TypeScript
144 lines
3.6 KiB
TypeScript
/**
|
|
* Next.js Middleware with Clerk Authentication
|
|
*
|
|
* This middleware protects routes using Clerk's clerkMiddleware.
|
|
* Place this file in the root of your Next.js project.
|
|
*
|
|
* Dependencies:
|
|
* - @clerk/nextjs@^6.33.3
|
|
*
|
|
* CRITICAL (v6 Breaking Change):
|
|
* - auth.protect() is now async - must use await
|
|
* - Source: https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6
|
|
*/
|
|
|
|
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
|
|
|
/**
|
|
* Define public routes (routes that don't require authentication)
|
|
*
|
|
* Glob patterns supported:
|
|
* - '/path' - exact match
|
|
* - '/path(.*)' - path and all sub-paths
|
|
* - '/api/public/*' - wildcard
|
|
*/
|
|
const isPublicRoute = createRouteMatcher([
|
|
'/', // Homepage
|
|
'/sign-in(.*)', // Sign-in page and sub-paths
|
|
'/sign-up(.*)', // Sign-up page and sub-paths
|
|
'/api/public(.*)', // Public API routes
|
|
'/api/webhooks(.*)', // Webhook endpoints
|
|
'/about', // Static pages
|
|
'/pricing',
|
|
'/contact',
|
|
])
|
|
|
|
/**
|
|
* Alternative: Define protected routes instead
|
|
*
|
|
* Uncomment this pattern if you prefer to explicitly protect
|
|
* specific routes rather than inverting the logic:
|
|
*/
|
|
/*
|
|
const isProtectedRoute = createRouteMatcher([
|
|
'/dashboard(.*)',
|
|
'/profile(.*)',
|
|
'/admin(.*)',
|
|
'/api/private(.*)',
|
|
])
|
|
|
|
export default clerkMiddleware(async (auth, request) => {
|
|
if (isProtectedRoute(request)) {
|
|
await auth.protect()
|
|
}
|
|
})
|
|
*/
|
|
|
|
/**
|
|
* Default Pattern: Protect all routes except public ones
|
|
*
|
|
* CRITICAL:
|
|
* - auth.protect() MUST be awaited (async in v6)
|
|
* - Without await, route protection will not work
|
|
*/
|
|
export default clerkMiddleware(async (auth, request) => {
|
|
if (!isPublicRoute(request)) {
|
|
await auth.protect()
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Matcher Configuration
|
|
*
|
|
* Defines which paths run middleware.
|
|
* This is the recommended configuration from Clerk.
|
|
*/
|
|
export const config = {
|
|
matcher: [
|
|
// Skip Next.js internals and static files
|
|
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
|
|
|
// Always run for API routes
|
|
'/(api|trpc)(.*)',
|
|
],
|
|
}
|
|
|
|
/**
|
|
* Advanced: Role-Based Protection
|
|
*
|
|
* Protect routes based on user role or organization membership:
|
|
*/
|
|
/*
|
|
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
|
|
|
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
|
|
const isOrgRoute = createRouteMatcher(['/org(.*)'])
|
|
|
|
export default clerkMiddleware(async (auth, request) => {
|
|
// Admin routes require 'admin' role
|
|
if (isAdminRoute(request)) {
|
|
await auth.protect((has) => {
|
|
return has({ role: 'admin' })
|
|
})
|
|
}
|
|
|
|
// Organization routes require organization membership
|
|
if (isOrgRoute(request)) {
|
|
await auth.protect((has) => {
|
|
return has({ permission: 'org:member' })
|
|
})
|
|
}
|
|
|
|
// All other routes use default protection
|
|
if (!isPublicRoute(request)) {
|
|
await auth.protect()
|
|
}
|
|
})
|
|
|
|
export const config = {
|
|
matcher: [
|
|
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
|
'/(api|trpc)(.*)',
|
|
],
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Troubleshooting:
|
|
*
|
|
* 1. Routes not protected?
|
|
* - Ensure auth.protect() is awaited
|
|
* - Check matcher configuration includes your routes
|
|
* - Verify middleware.ts is in project root
|
|
*
|
|
* 2. Infinite redirects?
|
|
* - Ensure sign-in/sign-up routes are in isPublicRoute
|
|
* - Check NEXT_PUBLIC_CLERK_SIGN_IN_URL in .env.local
|
|
*
|
|
* 3. API routes returning HTML?
|
|
* - Verify '/(api|trpc)(.*)' is in matcher
|
|
* - Check API routes are not in isPublicRoute if protected
|
|
*
|
|
* Official Docs: https://clerk.com/docs/reference/nextjs/clerk-middleware
|
|
*/
|