Skip to main content
Docs

Force multi-factor authentication (MFA) for all users

By default, Clerk does not enforce multi-factor authentication (MFA) for all users. This guide demonstrates how to force MFA for all users by using clerkMiddleware() to intercept all requests and check whether a user has MFA enabled. If the user does not have MFA enabled, clerkMiddleware() redirects them to the /mfa page where they can set up MFA.

Enable MFA in the Clerk Dashboard

If you haven't already, enable MFA for your users.

  1. In the Clerk Dashboard, navigate to the Multi-factor page.
  2. Toggle on the MFA strategies you would like to enable.

Customize the session token to include the two_factor_enabled property

Every User object has a two_factor_enabled property that indicates whether the user has MFA enabled. Store this property in the session token so that you can check it in your clerkMiddleware().

  1. In the Clerk Dashboard, navigate to the Sessions page.
  2. In the Customize session token section, select Edit.
  3. In the modal that opens, add a key-value pair to the Claims object. The key can be any string, but the value must be the user.two_factor_enabled property, as shown in the following example.
{
  "isMfa": "{{user.two_factor_enabled}}"
}

Update clerkMiddleware()

Update your clerkMiddleware() to check if the user has MFA enabled.

middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isMFARoute = createRouteMatcher(['/account/manage-mfa/add(.*)'])
const isSignInRoute = createRouteMatcher(['/sign-in(.*)'])

export default clerkMiddleware(async (auth, req) => {
  const { userId, sessionClaims } = await auth()

  // Redirect to homepage if the user is signed in and on the sign-in page
  if (userId !== null && isSignInRoute(req)) {
    return NextResponse.redirect(new URL('/', req.url))
  }

  // Redirect to MFA setup page if MFA is not enabled
  if (userId !== null && !isMFARoute(req)) {
    if (sessionClaims.isMfa === undefined) {
      console.error('You need to add the `isMfa` claim to your session token.')
    }
    if (sessionClaims.isMfa === false) {
      return NextResponse.redirect(new URL('/account/manage-mfa/add', req.url))
    }
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_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)(.*)',
  ],
}
src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'

const isMFARoute = createRouteMatcher(['/account/manage-mfa/add(.*)'])
const isSignInRoute = createRouteMatcher(['/sign-in(.*)'])

export const onRequest = clerkMiddleware((auth, context) => {
  const { userId, sessionClaims } = auth()

  // Redirect to homepage if the user is signed in and on the sign-in page
  if (userId !== null && isSignInRoute(context.request)) {
    return Response.redirect(new URL('/', context.request.url))
  }

  // Redirect to MFA setup page if MFA is not enabled
  if (userId !== null && !isMFARoute(context.request)) {
    if (sessionClaims.isMfa === undefined) {
      console.error('You need to add the `isMfa` claim to your session token.')
    }
    if (sessionClaims.isMfa === false) {
      return Response.redirect(new URL('/account/manage-mfa/add', context.request.url))
    }
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_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)(.*)',
  ],
}

By default, the Nuxt SDK automatically adds the clerkMiddleware() helper to your Nuxt application. To manually configure the middleware, in your nuxt.config.ts file, under the clerk property, set skipServerMiddleware: true.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@clerk/nuxt'],
  clerk: {
    skipServerMiddleware: true,
  },
})

Then, in your server/middleware/clerk.ts file, add the following code:

server/middleware/clerk.ts
import { clerkMiddleware } from '@clerk/nuxt/server'

export default clerkMiddleware(async (event) => {
  const isMFARoute = event.path.startsWith('/account/manage-mfa/add')
  const isSignInRoute = event.path.startsWith('/sign-in')

  const { userId, sessionClaims } = event.context.auth

  // Redirect to homepage if the user is signed in and on the sign-in page
  if (userId !== null && isSignInRoute) {
    throw createError({
      statusMessage: 'You are already signed in.',
    })
  }

  // Redirect to MFA setup page if MFA is not enabled
  if (userId !== null && !isMFARoute) {
    if (sessionClaims.isMfa === undefined) {
      throw createError({
        statusMessage: 'You need to add the `isMfa` claim to your session token.',
      })
    }
    if (sessionClaims.isMfa === false) {
      throw createError({
        statusMessage: 'You need to enable MFA for your account.',
      })
    }
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_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)(.*)',
  ],
}

Build your MFA setup page

Follow the custom flow guide to build the /account/manage-mfa/add page.

Feedback

What did you think of this content?

Last updated on