After the password, a code from an authenticator app on the phone, or one sent by e-mail: the password alone is then
useless to anyone else. This guide is for site owners and administrators who turn it on, and for users who set it up.
It is provided by the extension sevenx_authentication_2fa (composer.json requires it; the installers activate it).
The extension's own documentation has the details: extension/sevenx_authentication_2fa/README.md, INSTALL.md and
doc/two-factor-pages.md.
Time: 15 minutes to turn it on, 2 minutes per user to set it up.
What a user sees
Sign in as always, with username and password.
Confirm it is you: a page asks for the 6-digit code. With an authenticator app, open the app and type the code it shows for the site; with e-mail codes, the code is in the mail (and in its subject). Each code works once. After five wrong codes the sign-in ends and starts again with the password.
Set it up under My account > Two-step sign-in (
/user2fa/setup; in the administration the same address): choose Authenticator app, scan the QR code (or type the key shown in groups of four), and enter the code the app shows. Until that code is entered nothing changes. The key is not shown again afterwards.Change or turn off: switching to e-mail codes or off, or Reset 2FA, asks for a current code from the app, so someone at an unattended computer cannot weaken the account.
Keep the recovery codes: when the app is set up, the page shows ten recovery codes, once. Print them or keep them in a password manager. Each signs in once in place of the app's code (Use a recovery code on the code page); the setup page shows how many are left and makes a new set with a current code.
Lost the phone? Sign in with a recovery code and set the app up again. Without one, an administrator opens the user's object in the administration, sets the Two-step sign-in field to E-mail one-time code (or Disabled, where the second step is not required), and publishes. An administrator who is locked out is helped on the server:
php extension/sevenx_authentication_2fa/bin/php/sevenx2fauser.php --login=<login> # what the account has
php extension/sevenx_authentication_2fa/bin/php/sevenx2fauser.php --login=<login> --reset # remove it, unlock the accountAfter --reset the next sign-in asks for a new setup where the second step is required.
Turning it on
Activate the extension (
[ExtensionSettings] ActiveExtensions[]=sevenx_authentication_2fa) and the login handler insettings/override/site.ini.append.php, beforestandard:[UserSettings] LoginHandler[] LoginHandler[]=sevenxUser2fa LoginHandler[]=standardAdd the field to the user class: a class attribute of the datatype 7x 2FA Configuration (identifier for example
two_factor). Users of a class without it cannot keep a second step; the setup page says so.Give every role that signs in the policies
user2fa/setupanduser2fa/verify(Member, Editor, Administrator ...). Nothing is needed for anonymous visitors: the steps before signing in are in[RoleSettings] PolicyOmitListof the extension and check their own state.Recommended: a key that encrypts the authenticator secrets in the database, in
settings/override/sevenxauthentication2fa.ini.append.php(keep it out of version control; losing it means everyone sets the app up again):[TOTPSettings] SecretKey=<output of: openssl rand -base64 48>Decide whether it is everyone's choice or required:
[General] Enforce2FADefaultMethodEffect disabled(default)any Users opt in on the setup page. enabledtotpA user without a second step sets up the app right after the password, before being signed in. enabledemailA user without a second step gets e-mail codes straight away (the account's e-mail address must be real). Whom a required second step applies to:
Setting Effect EnforcedGroups[]Only members of these user groups (node id or object remote id), also through groups below them. Empty: every user. ExemptGroups[],ExemptEmailDomains[]Never asked (they may still opt in): members of these groups, and addresses in these domains, e.g. example.invalidfor the accounts of automated checks.GracePeriodDaysDays a user may still choose Set up later, from the first sign-in that asked. 0: the setup comes first.[Security] RecoveryCodesRecovery codes per authenticator (default 10, 0for none).A new installation is made with
Enforce2FA=enabledfor the groups Administrator users (9b47a45624b023b1a76c73b74d704acf) and Editors (3c160cca19fb135f83bd02d911f04db2), no exemptions and no grace period: whoever can change the site needs a second step, front-end members choose for themselves. The extension's own default staysdisabled, so updating it never locks anyone out of an existing site.Before requiring it,
php extension/sevenx_authentication_2fa/bin/php/sevenx2fauser.php --enforcedlists the accounts that would be asked to set it up at their next sign-in.Clear the caches and, for Velocity, deploy:
./console exp:velocity deploy --allow-root-user.
Try it on one siteaccess first: activate the extension as an access extension of a test siteaccess and put the
LoginHandler lines in that siteaccess's site.ini.append.php. Remember that settings/override outranks a
siteaccess: mail sent from the test siteaccess still uses the global [MailSettings] Transport.
On the demonstration server
Two-step sign-in is on at the demonstration server since 6 October 2026, as opt-in: the extension is active, the login
handler is sevenxUser2fa then standard, the user class has the field Two-step sign-in (two_factor), every role that signs
in (Member, Editor and the custom editor role; Administrator has all) has user2fa/setup and
user2fa/verify, and the secrets are encrypted with a key in
settings/override/sevenxauthentication2fa.ini.append.php. Nobody is enrolled by it: every account signs in as before
until its owner turns the second step on under My account.
To require it for administrators and editors, as a new installation does, set in
settings/override/sevenxauthentication2fa.ini.append.php:
[General]
Enforce2FA=enabled
DefaultMethod=totp
EnforcedGroups[]
EnforcedGroups[]=9b47a45624b023b1a76c73b74d704acf
EnforcedGroups[]=3c160cca19fb135f83bd02d911f04db2then clear the INI cache (php bin/php/ezcache.php --clear-tag=ini) and deploy (./console exp:velocity deploy).
Each of them without a second step then sets up an authenticator right after the password. Enrol the administrators
first, so nobody is caught without a phone at hand, and decide for the accounts of automated checks: exempt their
reserved domain (ExemptEmailDomains[]=example.invalid) or give each an authenticator from the console
(sevenx2fauser.php --login=<login> --enrol-totp --secret-file=<path>). A role made later needs user2fa/setup and
user2fa/verify for its users to set it up. To turn it off again, set Enforce2FA=disabled.
Signing in through WebDAV, the REST API's basic authentication, or code that calls eZUser::loginUser() directly
asks for the password only. Keep those off where the second step is required, or limited to accounts that do not need
it. A session signed in before the second step was required stays signed in until it ends.
Social login
Buttons such as Continue with Google appear on the login page (admin4, admin, Admin UI and the media design) for each
provider enabled in sevenxauthentication2fa.ini; INSTALL.md of the extension has the steps per provider. A user found
this way still has to pass their own second step. A cancelled or failed social login ends on a page that says what
happened, with the way back to the login form.
What protects it
- The password alone never signs in: the session only remembers that the password was right; the user is signed in, with a new session id, after the right code.
- Codes: each authenticator code works once and only within a minute or so of the clock; wrong codes are limited per
sign-in and count as failed logins of the account (
[UserSettings] MaxNumberOfFailedLoginlocks it); e-mail codes are kept hashed and a new one can be asked for once a minute, three times per sign-in; the link in the mail only works in the browser where the password was typed. - The QR code is drawn on the server; the key goes to no other service. Pages with a key or a pending sign-in are not cached.
- Every address the sign-in sends a visitor on to passes the safe redirect rules.
- Social login checks a one-time state bound to the provider and refuses addresses the provider has not verified.
- Every step is written to
var/log/auth.log(never a code or a secret).
Testing it
# the logic without a database
php vendor/bin/phpunit --no-configuration --bootstrap extension/sevenx_authentication_2fa/tests/bootstrap.php \
extension/sevenx_authentication_2fa/tests/unitBy use: sign in with a test user, set up the app, sign out, sign in with a code, try a wrong code and the same code twice, then turn it off with a current code. Never enrol a real administrator account while trying it out.
Become an Exponential Partner. Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster. Start with Partners: start here and How to apply.