Two-factor auth
TOTP authentication compatible with Google Authenticator and Authy.
Overview
Two-factor authentication is based on TOTP (Time-based One-Time Password), compatible with Google Authenticator, Authy and any standard TOTP client. The scheb/2fa-bundle automatically intercepts logins after password verification.
| Role | Detail |
|---|---|
| Business logic: secret generation, QR code, enable, disable, backup codes | src/Service/Auth/TwoFactorService.php |
| Routes for the activation and deactivation flow | src/Controller/Account/TwoFactorController.php |
| Activation page with QR code and code confirmation | templates/account/2fa_setup.html.twig |
| One-time display of backup codes after activation | templates/account/2fa_backup_codes.html.twig |
| TOTP code input form at login (rendered by scheb) | templates/auth/2fa.html.twig |
| scheb configuration: TOTP enabled, issuer, template | config/packages/scheb_2fa.yaml |
| main firewall: two_factor enabled with its routes | config/packages/security.yaml |
Dependencies
Three packages are required: the scheb bundle for interception, the TOTP provider, and endroid/qr-code to generate QR codes.
composer require scheb/2fa-bundle scheb/2fa-totp endroid/qr-code
Configuration
The bundle is configured in two files: scheb_2fa.yaml for the TOTP provider and security.yaml for the firewall.
Firewall (security.yaml)
The two_factor entry must be added to the main firewall for scheb to intercept logins.
config/packages/security.yaml
config/packages/scheb_2fa.yaml
User entity
The entity implements TotpTwoFactorInterface. Three database columns are used: two_factor_secret, two_factor_enabled and two_factor_backup_codes.
src/Entity/Auth/User.php
Activation flow
Activation takes place in two steps: scan the QR code, then confirm with a valid code.
-
1
The user clicks "Enable 2FA" from /account/security.
GET /account/security/2fa/setupa temporary secret is generated and stored in session, the QR code is rendered via endroid/qr-code.
-
2
The user scans the QR code with Google Authenticator, Authy or any compatible TOTP client.
-
3
The user enters the 6-digit code displayed by their app to confirm the setup.
POST /account/security/2fa/enablethe code is verified against the session secret via TotpAuthenticatorInterface::checkCode(). On failure, the QR code is re-displayed.
-
4
Backup codes are generated (8 hex codes of 8 characters) and displayed once.
GET /account/security/2fa/backup-codescodes are stored in the user's two_factor_backup_codes JSON column.
The temporary secret is stored in session during setup (key _2fa_temp_secret) and removed after successful activation. The database flush only happens after code validation.
Login with 2FA
When a user with 2FA enabled logs in, scheb/2fa-bundle automatically takes over after password verification.
-
1
The user enters their email and password — standard authentication.
-
2
scheb intercepts and issues a TwoFactorToken. The user is redirected to /2fa to enter their code.
GET /2fa → POST /2fa_check -
3
After code validation, the TwoFactorToken is replaced by a fully authenticated token and the user accesses the application.
Backup codes
Single-use backup codes (8 codes) are generated at activation. They allow access if the authentication device is lost.
Codes are stored as JSON in the two_factor_backup_codes column. Managing their consumption (single use) can be implemented if needed via InMemoryTwoFactorProviderInterface.
Backup codes are displayed only once after activation (session storage then deletion). The user must save them immediately.
Disabling
Disabling clears the TOTP secret, disables the flag and removes backup codes from the database.
POST /account/security/2fa/disable
clears totpSecret, sets totpEnabled → false, backupCodes → null, then flush.