Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions content/collections/pages/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,28 @@ Statamic uses your `APP_KEY` to encrypt the two-factor authentication secret and
You may run into issues with two-factor authentication if you have different `APP_KEY` values between environments *and* they share the same users (eg. you're tracking users in Git). You may want to disable 2FA locally in this case.
:::

### Frontend Two-Factor Authentication

Users who authenticate through your site's frontend (via [`{{ user:login_form }}`](/tags/user-login_form)) can also set up and challenge 2FA without ever touching the Control Panel. Statamic ships a set of tags for building those pages yourself:

- [`{{ user:two_factor_challenge_form }}`](/tags/user-two_factor_challenge_form) — the code verification form shown during login
- [`{{ user:two_factor_enable_form }}`](/tags/user-two_factor_enable_form) — step 1 of setup, generates the secret
- [`{{ user:two_factor_setup_form }}`](/tags/user-two_factor_setup_form) — step 2 of setup, displays the QR code and confirms the code
- [`{{ user:disable_two_factor_form }}`](/tags/user-disable_two_factor_form) — lets users turn 2FA off
- [`{{ user:two_factor_recovery_codes }}`](/tags/user-two_factor_recovery_codes) and [`{{ user:reset_two_factor_recovery_codes_form }}`](/tags/user-reset_two_factor_recovery_codes_form) — show and regenerate recovery codes
- [`{{ user:two_factor_enabled }}`](/tags/user-two_factor_enabled) — a boolean for conditionally rendering the above

When a user with 2FA enabled signs in on the frontend, Statamic redirects them to a challenge page. When 2FA is enforced for the user's role and they haven't set it up, Statamic redirects them to a setup page. Point these redirects at your own pages with the following config keys:

```php
// config/statamic/users.php

'two_factor_challenge_url' => '/account/2fa/challenge',
'two_factor_setup_url' => '/account/2fa/setup',
```

Leave either value `null` to use Statamic's built-in page for that step. Control Panel flows are unaffected — they always use their own pages.

## Passkeys

Statamic supports **passkeys** as a secure alternative to email-and-password logins. Passkeys are a passwordless authentication method built on WebAuthn and are supported by most modern operating systems and password managers. On macOS, iOS, and iPadOS, for example, you can sign in using Touch ID or Face ID.
Expand Down
72 changes: 72 additions & 0 deletions content/collections/tags/user-disable_two_factor_form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: User:Disable_Two_Factor_Form
description: Renders a form to disable 2FA on the user's account
intro: Allow users to turn off two-factor authentication. If their role requires 2FA, they'll be prompted to set it up again.
parameters:
-
name: redirect
type: string
description: Where the user should be taken after disabling 2FA.
-
name: allow_request_redirect
type: boolean
description: When set to true, the `redirect` parameter will get overridden by a `redirect` query parameter in the URL.
-
name: HTML Attributes
type:
description: >
Set HTML attributes as if you were in an HTML element. For example, `class="disable-form"`.
variables:
-
name: success
type: string
description: A success message.
id: 8f3d4a9b-0c2e-4f7a-3b6d-1e4f5a8b0c2d
---
## Overview

The `user:disable_two_factor_form` tag renders a form that allows authenticated users to disable two-factor authentication on their account. This removes the 2FA requirement and deletes their recovery codes.

The tag will render the opening and closing `<form>` HTML elements for you. No input fields are required—just a submit button.

:::tip
This form requires the user to be authenticated with 2FA enabled and an [elevated session](/tags/user-elevated_session_form). If the session isn't elevated, the user will be redirected to confirm their identity first.
:::

### Example

::tabs

::tab antlers
```antlers
{{ user:disable_two_factor_form redirect="/account" }}

{{ if success }}
<div class="bg-green-300 text-white p-2">
{{ success }}
</div>
{{ /if }}

<p>Are you sure you want to disable two-factor authentication?</p>
<button type="submit">Disable Two-Factor Authentication</button>

{{ /user:disable_two_factor_form }}
```
::tab blade
```blade
<s:user:disable_two_factor_form redirect="/account">
@if ($success)
<div class="bg-green-300 text-white p-2">
{{ $success }}
</div>
@endif

<p>Are you sure you want to disable two-factor authentication?</p>
<button type="submit">Disable Two-Factor Authentication</button>
</s:user:disable_two_factor_form>
```
::

## Enforced 2FA

If the user belongs to a role that has 2FA enforced (configured via `two_factor_enforced_roles` in your config), they can't really stay signed in with 2FA off — so after the form is submitted, Statamic ignores the `redirect` parameter and sends them to the setup page instead. That destination is pulled from the `statamic.users.two_factor_setup_url` config key in `config/statamic/users.php`, falling back to Statamic's built-in setup route if that's left `null`.
19 changes: 19 additions & 0 deletions content/collections/tags/user-login_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,22 @@ For more information on managing passkeys on the frontend, see the following doc
- [`{{ user:passkeys }}`](/tags/user-passkeys)
- [`{{ user:passkey_form }}`](/tags/user-passkey_form)
- [`{{ user:delete_passkey_form }}`](/tags/user-delete_passkey_form)

## Two-Factor Authentication

When a user with two-factor authentication (2FA) enabled submits the login form, Statamic will redirect them to a challenge page so they can enter a code from their authenticator app. If the user belongs to a role that requires 2FA but hasn't set it up yet, they'll be redirected to the setup page instead.

You can customize where each of these redirects goes using the `two_factor_challenge_url` and `two_factor_setup_url` config keys in `config/statamic/users.php`. Leave them `null` to use Statamic's built-in pages.

```php
// config/statamic/users.php

'two_factor_challenge_url' => '/account/2fa/challenge',
'two_factor_setup_url' => '/account/2fa/setup',
```

When rolling your own frontend pages, use the following tags to render the forms:

- [`{{ user:two_factor_challenge_form }}`](/tags/user-two_factor_challenge_form) — the code verification form during login
- [`{{ user:two_factor_enable_form }}`](/tags/user-two_factor_enable_form) — step 1 of setup, generates the secret
- [`{{ user:two_factor_setup_form }}`](/tags/user-two_factor_setup_form) — step 2 of setup, displays the QR code and confirms the code
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: User:Reset_Two_Factor_Recovery_Codes_Form
description: Renders a form to generate new recovery codes
intro: If a user has used some of their recovery codes or suspects they've been compromised, they can generate a fresh set. This invalidates all existing codes.
parameters:
-
name: redirect
type: string
description: Where the user should be taken after generating new codes.
-
name: HTML Attributes
type:
description: >
Set HTML attributes as if you were in an HTML element. For example, `class="reset-form"`.
variables:
-
name: success
type: string
description: A success message.
id: 7e2c3f8a-9b1d-4e6f-2a5c-0d3e4f7a9b1c
---
## Overview

The `user:reset_two_factor_recovery_codes_form` tag renders a form that allows authenticated users to generate a new set of recovery codes. When submitted, all existing recovery codes are invalidated and replaced with new ones.

The tag will render the opening and closing `<form>` HTML elements for you. No input fields are required—just a submit button.

:::tip
This form requires the user to be authenticated with 2FA enabled and an [elevated session](/tags/user-elevated_session_form). If the session isn't elevated, the user will be redirected to confirm their identity first.
:::

### Example

::tabs

::tab antlers
```antlers
{{ user:reset_two_factor_recovery_codes_form redirect="/account/recovery-codes" }}

{{ if success }}
<div class="bg-green-300 text-white p-2">
{{ success }}
</div>
{{ /if }}

<p>Generate new recovery codes? Your current codes will be invalidated.</p>
<button type="submit">Generate New Codes</button>

{{ /user:reset_two_factor_recovery_codes_form }}
```
::tab blade
```blade
<s:user:reset_two_factor_recovery_codes_form redirect="/account/recovery-codes">
@if ($success)
<div class="bg-green-300 text-white p-2">
{{ $success }}
</div>
@endif

<p>Generate new recovery codes? Your current codes will be invalidated.</p>
<button type="submit">Generate New Codes</button>
</s:user:reset_two_factor_recovery_codes_form>
```
::
98 changes: 98 additions & 0 deletions content/collections/tags/user-two_factor_challenge_form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: User:Two_Factor_Challenge_Form
description: Renders a form for users to enter their 2FA code during login
intro: When a user with two-factor authentication enabled logs in, they need to verify their identity with a code from their authenticator app. This tag renders that verification form.
parameters:
-
name: redirect
type: string
description: Where the user should be taken after successful verification.
-
name: error_redirect
type: string
description: Where the user should be redirected on validation errors.
-
name: allow_request_redirect
type: boolean
description: When set to true, the `redirect` and `error_redirect` parameters will get overridden by `redirect` and `error_redirect` query parameters in the URL.
-
name: HTML Attributes
type:
description: >
Set HTML attributes as if you were in an HTML element. For example, `class="required" id="2fa-form"`.
variables:
-
name: errors
type: array
description: An array of validation errors.
-
name: error
type: array
description: An array of validation errors indexed by field names. Suitable for targeting fields. eg. `{{ error:code }}`
-
name: success
type: string
description: A success message.
id: 3a8e9b4c-5d7f-4a2b-8c1e-6f9d0a3b5c7e
---
## Overview

The `user:two_factor_challenge_form` tag renders a form for users to complete the second step of two-factor authentication. After submitting valid credentials on the login form, users with 2FA enabled are redirected to this challenge form.

The tag will render the opening and closing `<form>` HTML elements for you. Users can verify their identity by entering either a `code` from their authenticator app or a `recovery_code`.

:::tip
This form will only render content if there's a pending 2FA challenge in the session. If accessed without a pending challenge, the form contents won't be displayed.
:::

### Example

::tabs

::tab antlers
```antlers
{{ user:two_factor_challenge_form redirect="/dashboard" }}

{{ if errors }}
<div class="bg-red-300 text-white p-2">
{{ errors }}
{{ value }}<br>
{{ /errors }}
</div>
{{ /if }}

<p>Enter the 6-digit code from your authenticator app:</p>
<input type="text" name="code" inputmode="numeric" autocomplete="one-time-code" maxlength="6" />

<p>Or use a recovery code:</p>
<input type="text" name="recovery_code" />

<button type="submit">Verify</button>

{{ /user:two_factor_challenge_form }}
```
::tab blade
```blade
<s:user:two_factor_challenge_form redirect="/dashboard">
@if ($errors)
<div class="bg-red-300 text-white p-2">
@foreach ($errors as $error)
{{ $error }}<br>
@endforeach
</div>
@endif

<p>Enter the 6-digit code from your authenticator app:</p>
<input type="text" name="code" inputmode="numeric" autocomplete="one-time-code" maxlength="6" />

<p>Or use a recovery code:</p>
<input type="text" name="recovery_code" />

<button type="submit">Verify</button>
</s:user:two_factor_challenge_form>
```
::

## Redirect Behavior

If you don't specify a `redirect` parameter, the form will use the `redirect` value from the original login form. This allows you to specify the redirect destination once on the login form and have it carry through the entire 2FA flow.
92 changes: 92 additions & 0 deletions content/collections/tags/user-two_factor_enable_form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: User:Two_Factor_Enable_Form
description: Renders the first step of 2FA setup — generates the user's 2FA secret
intro: Step one of the two-factor authentication setup flow. Submitting this form generates a 2FA secret for the user and sends them on to the setup page where they confirm the code.
parameters:
-
name: redirect
type: string
description: Where the user should be taken after their 2FA secret has been generated. Typically this is the page that renders `{{ user:two_factor_setup_form }}`.
-
name: allow_request_redirect
type: boolean
description: When set to true, the `redirect` parameter will get overridden by a `redirect` query parameter in the URL.
-
name: HTML Attributes
type:
description: >
Set HTML attributes as if you were in an HTML element. For example, `class="required" id="2fa-enable-form"`.
variables:
-
name: errors
type: array
description: An array of validation errors.
-
name: error
type: array
description: An array of validation errors indexed by field names.
-
name: success
type: string
description: A success message.
id: 8f4c6868-0aee-4814-a498-a4e118c2f400
---
## Overview

The `user:two_factor_enable_form` tag renders **step one** of the two-factor authentication setup flow. Submitting it generates the user's 2FA secret (and eventually their recovery codes, once they confirm), then redirects them on to the setup page where the QR code and confirmation form are displayed.

The tag will render the opening and closing `<form>` HTML elements for you. No input fields are required — just a submit button.

:::tip
This form only renders for an authenticated user who does **not** already have 2FA enabled. It also requires an [elevated session](/tags/user-elevated_session_form) — if the session isn't elevated, the user will be redirected to confirm their identity first.
:::

### Example

::tabs

::tab antlers
```antlers
{{ user:two_factor_enable_form redirect="/account/2fa/setup" }}

{{ if errors }}
<div class="bg-red-300 text-white p-2">
{{ errors }}
{{ value }}<br>
{{ /errors }}
</div>
{{ /if }}

<p>Click below to start setting up two-factor authentication. You'll be taken to a page where you can scan a QR code with your authenticator app.</p>

<button type="submit">Set Up Two-Factor Authentication</button>

{{ /user:two_factor_enable_form }}
```
::tab blade
```blade
<s:user:two_factor_enable_form redirect="/account/2fa/setup">
@if ($errors)
<div class="bg-red-300 text-white p-2">
@foreach ($errors as $error)
{{ $error }}<br>
@endforeach
</div>
@endif

<p>Click below to start setting up two-factor authentication. You'll be taken to a page where you can scan a QR code with your authenticator app.</p>

<button type="submit">Set Up Two-Factor Authentication</button>
</s:user:two_factor_enable_form>
```
::

## Redirect behavior

After the secret is generated, Statamic decides where to send the user in the following order:

1. The form's `redirect` parameter (submitted as `_redirect`).
2. The `statamic.users.two_factor_setup_url` config key in `config/statamic/users.php`.
3. The referring page (i.e. back to where the form was rendered).

Whichever destination is used, that page should render [`{{ user:two_factor_setup_form }}`](/tags/user-two_factor_setup_form) so the user can complete setup.
Loading