Skip to content

Commit

Permalink
feat: Introduce Reverification (#1726)
Browse files Browse the repository at this point in the history
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
5 people authored Nov 27, 2024
1 parent ac79d69 commit 5b19f54
Show file tree
Hide file tree
Showing 7 changed files with 675 additions and 21 deletions.
20 changes: 20 additions & 0 deletions docs/_partials/reverification-route-handler.mdx
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 })
}
```
202 changes: 202 additions & 0 deletions docs/guides/reverification.mdx
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.
15 changes: 15 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@
"title": "Architecture scenarios",
"href": "/docs/guides/architecture-scenarios"
},
{
"title": "Add reverification for sensitive actions",
"tag": "(Beta)",
"href": "/docs/guides/reverification"
},
{
"title": "Routing in Clerk",
"href": "/docs/guides/routing"
Expand Down Expand Up @@ -1919,6 +1924,12 @@
"title": "`useOrganizationList()`",
"wrap": false,
"href": "/docs/references/react/use-organization-list"
},
{
"title": "`useReverification()`",
"tag": "(Beta)",
"wrap": false,
"href": "/docs/references/react/use-reverification"
}
]
]
Expand Down Expand Up @@ -2181,6 +2192,10 @@
"title": "SessionStatus",
"href": "/docs/references/javascript/types/session-status"
},
{
"title": "SessionVerification",
"href": "/docs/references/javascript/types/session-verification"
},
{
"title": "SignInFirstFactor",
"href": "/docs/references/javascript/types/sign-in-first-factor"
Expand Down
Loading

0 comments on commit 5b19f54

Please sign in to comment.