The clerkMiddleware() helper allows you to protect your Nuxt application on the server-side. It can be used to validate a user's authentication status or authorization status.
Warning
clerkMiddleware() should be used to protect API routes only. It's not recommended to use clerkMiddleware() to protect pages as it will only work on initial page reload. On subsequent navigations, it won't be triggered because client-side navigation will bypass the middleware. To protect pages, see the dedicated guide.
To protect routes based on user authentication status, you can check if the user is signed in by checking the isAuthenticated property on the authNuxt.js Icon object.
In the following example, the clerkMiddleware() helper checks if the user is signed in and accessing a protected route. If they aren't signed in, an error is thrown using Nuxt's createError() utility.
server/middleware/clerk.ts
import { clerkMiddleware } from'@clerk/nuxt/server'exportdefaultclerkMiddleware((event) => {const { isAuthenticated } =event.context.auth()constisAdminRoute=event.path.startsWith('/api/admin')if (!isAuthenticated && isAdminRoute) {throwcreateError({ statusCode:401, statusMessage:'Unauthorized: User not signed in', }) }})
In the following example, the clerkMiddleware() helper checks if the user is accessing a protected route. If so, it checks if the user has the required Custom Permission. If they don't, an error is thrown using Nuxt's createError() utility.
server/middleware/clerk.ts
import { clerkMiddleware } from'@clerk/nuxt/server'exportdefaultclerkMiddleware((event) => {const { has } =event.context.auth()constisInvoicesRoute=event.path.startsWith('/api/invoices')constcanCreateInvoices=has({ permission:'org:invoices:create', })// Check if the user is accessing a protected routeif (isInvoicesRoute) {// Check if the user has the required Permissionif (!canCreateInvoices) {throwcreateError({ statusCode:403, statusMessage:'Unauthorized: Missing Permission to create invoices', }) } }})
It's best practice to use Permission-based authorization over Role-based authorization, as it reduces complexity and increases security. Usually, complex Role checks can be refactored with a single Permission check.
In the following example, the clerkMiddleware() helper checks if the user is accessing a protected route. If so, it checks if the user has the required admin Role. If they don't, an error is thrown using Nuxt's createError() utility.
server/middleware/clerk.ts
import { clerkMiddleware } from'@clerk/nuxt/server'exportdefaultclerkMiddleware((event) => {const { has } =event.context.auth()constisAdminRoute=event.path.startsWith('/api/admin')constisAdmin=has({ role:'org:admin', })// Check if the user is accessing a protected routeif (isAdminRoute) {// Check if the user has the required Roleif (!isAdmin) {throwcreateError({ statusCode:403, statusMessage:'Unauthorized: Admin access required', }) } }})
You can protect multiple routes at once by using Clerk's createRouteMatcher() helper function. The createRouteMatcher() helper accepts an array of route patterns and checks if the route the user is trying to visit matches one of the patterns passed to it.
Let's take the first exampleNuxt.js Icon from this guide and add the createRouteMatcher() helper. Instead of only checking /api/admin/** routes, the following example checks both /api/invoices/**and/api/admin/** routes.
server/middleware/clerk.ts
+ import { clerkMiddleware, createRouteMatcher } from'@clerk/nuxt/server'exportdefaultclerkMiddleware((event) => {const { isAuthenticated } =event.context.auth()- constisAdminRoute=event.path.startsWith('/api/admin')+ constisProtectedRoute=createRouteMatcher(['/api/invoices(.*)','/api/admin(.*)'])// Check if the user is not signed in// and is trying to access a protected route. If so, throw a 401 error.if (!isAuthenticated &&isProtectedRoute(event)) {throwcreateError({ statusCode:401, statusMessage:'Unauthorized: User not signed in', }) }})
Now, let's add authorization-based protection to the example so that you can see how to combine everything you've learned so far.
server/middleware/clerk.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nuxt/server'exportdefaultclerkMiddleware((event) => {const { isAuthenticated } =event.context.auth()constisProtectedRoute=createRouteMatcher(['/api/invoices(.*)','/api/admin(.*)'])+ constcanCreateInvoices=has({+ permission:'org:invoices:create',+ })// Check if the user is not signed in// and is trying to access a protected route. If so, throw a 401 error.if (!isAuthenticated &&isProtectedRoute(event)) {throwcreateError({ statusCode:401, statusMessage:'Unauthorized: User not signed in', }) }+ // Check if the user doesn't have the required Permission+ // and is accessing a protected route. If so, throw a 403 error.+ if (!canCreateInvoices &&isProtectedRoute(event)) {+ throwcreateError({+ statusCode:403,+ statusMessage:'Unauthorized: Missing Permission to create invoices',+ })+ }})
The clerkMiddleware() function accepts an optional object. The following options are available:
Name
audience?
Type
string | string[]
Description
A string or list of audiences. If passed, it is checked against the aud claim in the token.
Name
authorizedParties?
Type
string[]
Description
An allowlist of origins to verify against, to protect your application from the subdomain cookie leaking attack. For example: ['http://localhost:3000', 'https://example.com']
Name
clockSkewInMs?
Type
number
Description
Specifies the allowed time difference (in milliseconds) between the Clerk server (which generates the token) and the clock of the user's application server when validating a token. Defaults to 5000 ms (5 seconds).
Name
domain?
Type
string
Description
The domain used for satellites to inform Clerk where this application is deployed.
Name
isSatellite?
Type
boolean
Description
When using Clerk's satellite feature, this should be set to true for secondary domains.
Name
jwtKey
Type
string
Description
Used to verify the session token in a networkless manner. Supply the JWKS Public Key from the API keys page in the Clerk Dashboard. It's recommended to use the environment variable instead. For more information, refer to Manual JWT verification.
Used to activate a specific Organization or Personal Account based on URL path parameters. If there's a mismatch between the Active Organization in the session (e.g., as reported by auth()Next.js Icon) and the Organization indicated by the URL, the middleware will attempt to activate the Organization specified in the URL.
Name
proxyUrl?
Type
string
Description
Specify the URL of the proxy, if using a proxy.
Name
signInUrl
Type
string
Description
The full URL or path to your sign-in page. Needs to point to your primary application on the client-side. Required for a satellite application in a development instance. It's recommended to use the environment variable instead.
Name
signUpUrl
Type
string
Description
The full URL or path to your sign-up page. Needs to point to your primary application on the client-side. Required for a satellite application in a development instance. It's recommended to use the environment variable instead.
Name
publishableKey
Type
string
Description
The Clerk Publishable Key for your instance.
Name
secretKey?
Type
string
Description
The Clerk Secret Key for your instance. The CLERK_ENCRYPTION_KEY environment variable must be set when providing secretKey as an option, refer to Dynamic keysNuxt.js Icon.
The organizationSyncOptions property on the clerkMiddleware()Nuxt.js Icon options
object has the type OrganizationSyncOptions, which has the following properties:
Specifies URL patterns that are Organization-specific, containing an Organization ID or slug as a path parameter. If a request matches this path, the Organization identifier will be used to set that Organization as active.
If the route also matches the personalAccountPatterns prop, this prop takes precedence.
Patterns must have a path parameter named either :id (to match a Clerk Organization ID) or :slug (to match a Clerk Organization slug).
Warning
If the Organization can't be activated—either because it doesn't exist or the user lacks access—the previously Active Organization will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling notFound() or displaying an <OrganizationSwitcher />.