Odoo 16 and later ships with native TOTP-based two-factor authentication. Users scan a QR code in an authenticator app and enter a 6-digit code at each login. Administrators can enforce 2FA for specific user groups without additional modules.
Enabling 2FA in Settings#
Settings → General Settings → Permissions → Two-Factor Authentication
Three policy options:
- Optional - users may enable 2FA voluntarily
- Required for administrators - users in base.group_system must enable 2FA
- Required for all users - every internal user must enroll; they are redirected to the enrollment wizard on next login
Portal users are never forced into 2FA regardless of the policy.
User Enrollment Flow#
When a user first logs in under a "required" policy without 2FA enabled:
- They see the 2FA enrollment page instead of the dashboard
- A QR code and manual entry key are displayed
- The user scans with their authenticator app and enters the current code to confirm
- Odoo stores the TOTP secret (encrypted) on the
res.usersrecord
Subsequent logins present the password prompt first, then a second screen for the 6-digit code. The session is not established until both steps succeed.
Backup Codes#
After TOTP enrollment, users generate backup codes under My Profile → Account Security → Generate Backup Codes. Each code is single-use. Odoo generates 8 codes stored as hashed values in res.users_authenticator. They are separate from the TOTP secret.
Administrator Recovery (Lost Device)#
If a user loses their authenticator and has no backup codes, an administrator can reset:
- Settings → Users & Companies → Users → open the user
- Click Reset Two-Factor Authentication (Security tab)
- Confirm the dialog
This deletes the TOTP secret and remaining backup codes. The user must re-enroll on next login.
Programmatic Enforcement per Group#
<record id="base.group_sale_manager" model="res.groups">
<field name="totp_required">1</field>
</record>When totp_required is True on any group a user belongs to, that user is subject to 2FA enforcement even if the global policy is "Optional". Group-level enforcement is additive.
Checking Enrollment Status#
users_without_2fa = env['res.users'].search([
('share', '=', False),
('totp_secret', '=', False),
])Common Mistakes#
- Enforcing 2FA before users are warned - users mid-session are logged out on next request; send a communication before enabling "Required for all users"
- Shared accounts with TOTP - TOTP enrollment ties the secret to one device; use separate user accounts with POS-specific groups rather than sharing an account
- Portal user 2FA confusion - portal users cannot be required to use TOTP; implement MFA at the identity provider layer (e.g., via SAML SSO) for portal access
- Clock drift on OTP device - TOTP codes are time-based; ensure NTP is running on the Odoo host so the server clock stays accurate

