-
Notifications
You must be signed in to change notification settings - Fork 484
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Introduce Reverification (#1726)
Co-authored-by: Bryce Kalow <[email protected]> Co-authored-by: Colin Sidoti <[email protected]> Co-authored-by: Alexis Aguilar <[email protected]> Co-authored-by: victoria <[email protected]>
- Loading branch information
1 parent
ac79d69
commit 5b19f54
Showing
7 changed files
with
675 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
The following example uses the [`has()`](/docs/references/nextjs/auth-object#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. | ||
|
||
```tsx {{ filename: 'app/api/reverification-example/route.ts' }} | ||
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 }) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
--- | ||
title: Add reverification for sensitive actions | ||
description: Learn how to implement Clerk's reverification feature to protect sensitive actions in your application. | ||
--- | ||
|
||
> [!WARNING] | ||
> This feature is currently in public beta. **It is not recommended for production use**. | ||
> | ||
> Depending on the SDK you're using, this feature requires `@clerk/[email protected]` or later, `@clerk/[email protected]` or later, and `@clerk/[email protected]` or later. | ||
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()`](/docs/references/nextjs/auth-object#has) helper to check if the user has verified their credentials within a specific time period. Pass a [configuration](#supported-verification-configurations) 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. | ||
|
||
<Tabs items={["Next.js Server Action", "Next.js Route Handler", "Ruby on Rails", "Other"]}> | ||
<Tab> | ||
The following example uses the [`has()`](/docs/references/nextjs/auth-object#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. | ||
|
||
```ts {{ filename: '/app/actions.ts' }} | ||
'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 } | ||
} | ||
``` | ||
</Tab> | ||
|
||
<Tab> | ||
<Include src="_partials/reverification-route-handler" /> | ||
</Tab> | ||
|
||
<Tab> | ||
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. | ||
|
||
```ruby {{ filename: 'app/controllers/home_controller.rb' }} | ||
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 | ||
``` | ||
</Tab> | ||
|
||
<Tab> | ||
> [!WARNING] | ||
> `reverificationErrorResponse` and `reverificationError` requires `@clerk/[email protected]` or later, and `@clerk/[email protected]` or later. | ||
- For a JavaScript or Typescript framework that supports the [Fetch API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), use the `reverificationErrorResponse` to trigger reverification. For example: | ||
<CodeBlockTabs options={["Astro"]}> | ||
```typescript {{ filename: '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 }) | ||
} | ||
``` | ||
</CodeBlockTabs> | ||
- For a JavaScript or Typescript framework that provides its own utilities to handle responses, use `reverificationError`. For example: | ||
<CodeBlockTabs options={["Hono"]}> | ||
```typescript {{ filename: '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')) | ||
}) | ||
``` | ||
</CodeBlockTabs> | ||
- 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): | ||
```json | ||
{ | ||
"clerk_error": { | ||
"type": "forbidden", | ||
"reason": "reverification-error" | ||
} | ||
} | ||
``` | ||
</Tab> | ||
</Tabs> | ||
|
||
### 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()`](/docs/references/react/use-reverification) 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. | ||
|
||
<Tabs items={["Next.js Server Action", "Next.js Route Handler", "React"]}> | ||
<Tab> | ||
```tsx {{ filename: '/app/perform-action/page.tsx' }} | ||
'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> | ||
} | ||
``` | ||
</Tab> | ||
|
||
<Tab> | ||
```tsx {{ filename: '/app/transfer/page.tsx' }} | ||
'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> | ||
} | ||
``` | ||
</Tab> | ||
|
||
<Tab> | ||
```tsx {{ filename: '/src/components/TransferButton.js' }} | ||
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> | ||
} | ||
``` | ||
</Tab> | ||
</Tabs> | ||
|
||
## 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](/docs/references/nextjs/auth-object#check-authorization-params-with-custom-permissions) for more details. | ||
|
||
## Caveats | ||
|
||
Before enabling this feature, consider the following: | ||
|
||
1. **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 | ||
1. **Graceful downgrade of verification level**: If you request a `second_factor` or `multi_factor` level of verification but the user lacks a second factor available, the utilities automatically downgrade the requested level to `first_factor`. | ||
1. **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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.