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.
How to require reverification
To implement reverification, you need to handle it both on the server- and client-side.
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. 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.
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>
}
Supported reverification configurations
To define the time period of the reverification check, you can pass the one of the following configurations to the has()
helper: strict_mfa
, strict
, moderate
, and lax
. See the has()
reference doc for more details.
Caveats
Before enabling this feature, consider the following:
- Available factors for reverification: Not all authentication factors are supported for reverification. The available options are:
- 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.
Feedback
Last updated on