Add reverification for sensitive actions
Reverification allows you to prompt a user to verify their credentials before performing sensitive actions, even if they're already authenticated. For example, in a banking application, transferring money is considered a "sensitive action." Reverification can be used to confirm the user's identity.
Clerk's prebuilt components handle reverification for certain sensitive actions, out of the box. However, this guide will show you how to implement reverification for sensitive actions that are unique to your application.
Sensitive actions that require reverification
The following table shows the sensitive actions that require reverification along with the required strategy and timeframe for each action. When using Clerk's prebuilt components, reverification is automatically handled for these actions.
Action | Strategy | Timeframe |
---|---|---|
Update username | Strongest available | 10m |
Set/update password | Strongest available | 10m |
Add/remove email address | Strongest available | 10m |
Add/remove phone number | Strongest available | 10m |
Add/remove Web3 Wallet | Strongest available | 10m |
Add/remove passkey | Strongest available | 10m |
Set primary identification | Strongest available | 10m |
Connect/remove external account | Strongest available | 10m |
Add/remove MFA (TOTP, phone number, backup codes) | Strongest available | 10m |
Revoke session | Strongest available | 10m |
Delete account | Strongest available | 10m |
Caveats
Before enabling this feature, consider the following:
- Available factors for reverification: Not all authentication factors are supported for reverification. Users can only reverify their credentials using the following factors:
- First factors: password, email code, phone code
- Second factors: phone code, authenticator app, backup code
- Graceful downgrade of verification level: If you request a
second_factor
ormulti_factor
level of verification but the user lacks a second factor available, the utilities automatically downgrade the requested level tofirst_factor
. - Eligibility for sensitive actions: Users without any of the above factors cannot reverify. This can be an issue for apps that don't require email addresses to sign up or have disabled email codes in favor of email links.
How to require reverification
The simplest way to require reverification for a sensitive action is to use the useReverification()
hook. This hook will automatically check if the user has verified their credentials within 10 minutes and if not, displays a modal that prompts the user to verify their credentials. See the reference doc for more information and comprehensive examples.
However, you may have sensitive actions on the server side that you want to require reverification for, such as in a Server Action or Route Handler. In this case, you can use the auth.has()
helper to check if the user has verified their credentials within a specific time period. This only does the check on the server side, so you still need to handle reverification on the client side in order for your users to be able to verify their credentials. See the examples below for more information.
Handle reverification server-side
To handle reverification server-side, use the auth.has()
helper to check if the user has verified their credentials within a specific time period. Pass a configuration to set the time period you would like. You can pass one of the following configurations: strict_mfa
, strict
, moderate
, and lax
. See the reference doc for details on each configuration.
If the user hasn't verified their credentials within that time period, return either reverificationError
or reverificationErrorResponse
, depending on the framework you're using. Use the following tabs to see examples for different frameworks.
The following example uses the has()
helper to check if the user has verified their credentials within a specific time period. The strict
configuration sets the time period to 10 minutes. If the user hasn't verified their credentials within 10 minutes, the reverificationError
utility is used to return an error.
'use server'
import { auth, reverificationError } from '@clerk/nextjs/server'
export const myAction = async () => {
const { has } = await auth.protect()
// Check if the user has *not* verified their credentials within the past 10 minutes
const shouldUserRevalidate = !has({ reverification: 'strict' })
// If the user hasn't reverified, return an error with the matching configuration (e.g. `strict`)
if (shouldUserRevalidate) {
return reverificationError('strict')
}
// If the user has verified credentials, return a successful response
return { success: true }
}
The following example uses the has()
helper to check if the user has verified their credentials within a specific time period. The strict
configuration sets the time period to 10 minutes. If the user hasn't verified their credentials within 10 minutes, the reverificationErrorResponse
utility is used to return an error.
import { auth, reverificationErrorResponse } from '@clerk/nextjs/server'
export const POST = async (req: Request) => {
const { has } = await auth()
// Check if the user has *not* verified their credentials within the past 10 minutes.
const shouldUserRevalidate = !has({ reverification: 'strict' })
// If the user hasn't reverified, return an error with the matching configuration (e.g., `strict`)
if (shouldUserRevalidate) {
return reverificationErrorResponse('strict')
}
// If the user has verified credentials, return a successful response
return new Response({ success: true })
}
The following example uses the clerk_user_needs_reverification
helper to check if the user has verified their credentials within a specific time period. The moderate
configuration sets the time period to 1 hour. If the user hasn't verified their credentials within 1 hour, the clerk_render_reverification
utility is used to return a 403 forbidden
error that the client reads to initiate the reverification flow. Once the user completes the reverification on the client-side, they can access the foo
action, which returns a success response.
class HomeController < ApplicationController
before_action :require_reverification, only: :foo
def foo
render json: { success: "true" }
end
private
# will halt the request and respond with a JSON that Clerk.js
# will read and kickstart a reverification flow
def require_reverification
clerk_render_reverification(Clerk::StepUp::PRESETS[:moderate]) if clerk_user_needs_reverification?(Clerk::StepUp::PRESETS[:moderate])
end
end
- For a JavaScript or Typescript framework that supports the Fetch API
Response
, use thereverificationErrorResponse
to trigger reverification. For example:src /api /reverification-example.ts import type { APIRoute } from 'astro' import { reverificationErrorResponse } from '@clerk/shared/authorization-errors' export const GET: APIRoute = async ({ locals }) => { const { has } = locals.auth() // Check if the user has *not* verified their credentials within the past 10 minutes const shouldUserRevalidate = !has({ reverification: 'strict' }) // If the user hasn't reverified, return an error with the matching configuration (e.g. `strict`) if (shouldUserRevalidate) { return reverificationErrorResponse('strict') } return new Response('success', { status: 200 }) }
- For a JavaScript or Typescript framework that provides its own utilities to handle responses, use
reverificationError
. For example:src /api /hello.ts import { Hono } from 'hono' const app = new Hono() // POST request route app.post('/api/hello', async (c) => { return c.json(reverificationError('strict')) })
- Alternatively, if you're not using JavaScript or TypeScript, you can create a custom helper that returns the following JSON response (it's recommended to use a
403 Forbidden
status code in your response):{ "clerk_error": { "type": "forbidden", "reason": "reverification-error" } }
Handle reverification client-side
After setting up reverification on the server-side, you must handle reverification on the client-side so that your users can verify their credentials.
The following example demonstrates how to use the useReverification()
hook to detect authorization errors and automatically display a modal that allows the user to verify their identity. Upon successful verification, the previously failed request is automatically retried.
'use client'
import { useReverification } from '@clerk/nextjs'
import { myAction } from '../actions'
export default function Page() {
const performAction = useReverification(myAction)
const handleClick = async () => {
const myData = await performAction()
// If `myData` is null, the user cancelled the reverification process
// You can choose how your app responds. This example returns null.
if (!myData) return
}
return <button onClick={handleClick}>Perform action</button>
}
'use client'
import { useReverification } from '@clerk/nextjs'
export default function Page({ amount_in_cents }: { amount_in_cents: number }) {
const transferMoney = useReverification(
async () =>
await fetch('/api/reverification-example', {
method: 'POST',
body: JSON.stringify({ amount_in_cents }),
}),
)
return <button onClick={transferMoney}>Transfer</button>
}
import { useReverification } from '@clerk/react'
export function TransferButton({ amount_in_cents }: { amount_in_cents: number }) {
const transferMoney = useReverification(() =>
fetch('/api/reverification-example', {
method: 'POST',
body: JSON.stringify({ amount_in_cents }),
}),
)
return <button onClick={transferMoney}>Transfer</button>
}
Feedback
Last updated on