Merged
Conversation
Also, remove the "not on user show page" logic since that should be the only page is used on now.
Also, add the ability to copy/download them.
Users will only end up here when 2FA is enforced for their account.
We can add this back in the future if we need it.
This prevents the log out route working if you're logged in but dont have permission to access the cp. This is probably worthwhile to resolve but out of scope for this PR.
… dispatch an event but should.
…to disable other users on the existing routes
…or yourself. Use the action to disable for others.
jasonvarga
approved these changes
May 1, 2025
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request implements Two-Factor Authentication. Heavily inspired by the "Two Factor for Statamic" addon by Mity Digital (thanks @martyf and @mscruse!).
Usage
You can enable two-factor authentication from your user profile:
CleanShot.2025-04-24.at.14.36.33.mp4
Then, whenever you login to the Control Panel, you'll be asked to provide either a one-time code from your authenticator app (eg Google Authenticator, 1Password) or a recovery code:
CleanShot.2025-04-24.at.14.37.53.mp4
When you provide a recovery code, the used code will be replaced and an email will be sent allowing you to save the updated set of recovery codes.
Two Factor Authentication is opt-in by default. However, if necessary, you can force users to enable 2FA based on their roles:
Users who haven't already enabled 2FA will be prompted to do so when they next login:
^ Keeping the UI ugly but functional for now. We'll worry about the design after the
uibranch has been merged intomaster.Technical Approach
Profile
The "Two Factor" section on the user profile page is handled by a custom fieldtype.
It's hidden from the blueprint field selector, and it doesn't actually save anything. It's just a vehicle for showing what we need to show in the user publish form.
Enabling, disabling, and viewing recovery codes all result in AJAX requests being sent off to the backend.
Users with the
edit userspermission can disable two-factor authentication for other users.Two Factor challenge
In order for users to be redirected to the challenge page when they're logged in, we needed to make some changes to the login process.
So, instead of us authenticating users right away, like we used to, we're now "validating" their credentials first.
If they're valid, and the user has 2FA enabled, we set a
login.idkey in the session identifying the user we want to login, and redirect the user along to the challenge page.When the user completes the two-factor challenge, we validate the provided one-time code / recovery code, and attempt to login the user using the
login.idkey stored in the session.This is an approach I've borrowed from Laravel Fortify, which they also seem to be adopting in the new Laravel starter kits.
Frontend
This pull request implements Two Factor Authentication for the Control Panel. We'll tackle 2FA and frontend forms in a separate PR.
However, users logging in via the
{{ user:login_form }}tag will still be taken to the two-factor challenge / setup pages, if necessary.The frontend flow uses the same code as the Control Panel flow, just with different routes/middlewares.
Storage
When you enable two-factor authentication, three keys will be saved in your user data:
two_factor_secrettwo_factor_recovery_codestwo_factor_confirmed_atThe
two_factor_secretandtwo_factor_recovery_codesvalues are encrypted using your applications's encryption key (APP_KEY).Warning
You may run into issues with two-factor authentication if you have different
APP_KEYvalues between environments and they share the same users (eg. you're tracking users in Git).If you're storing users in the database, a migration should be published during the upgrade process to add the columns to your
userstable:Statamic will also attempt to add a cast for the
two_factor_confirmed_atcolumn to yourUsermodel:protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'preferences' => 'json', ++ 'two_factor_confirmed_at' => 'datetime', ]; }Requires #11688.
Related: statamic/ideas#1047
Docs: statamic/docs#1671