Multi-Factor Authentication (MFA)
Complete guide to MFA setup including TOTP, WebAuthn, passkeys, and recovery codes
Multi-Factor Authentication (MFA)
The system supports multiple MFA methods through django-allauth's MFA module.
Supported Methods
| Method | Status | Description |
|---|---|---|
| TOTP | ✅ Enabled | Time-based One-Time Password (Google Authenticator, etc.) |
| WebAuthn | ✅ Enabled | Hardware keys and biometrics (YubiKey, Touch ID, etc.) |
| Recovery Codes | ✅ Enabled | Backup codes for account recovery |
| Passkey Login | ✅ Enabled | Passwordless login via WebAuthn |
Configuration
# config/settings.py
MFA_SUPPORTED_TYPES = ["totp", "webauthn", "recovery_codes"]
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = DEBUG # Allow localhost in dev
MFA_PASSKEY_LOGIN_ENABLED = True
MFA_PASSKEY_SIGNUP_ENABLED = False
USERSESSIONS_TRACK_ACTIVITY = TrueTOTP Setup
Step 1: Get TOTP Secret
GET /_allauth/browser/v1/account/authenticators/totpResponse:
{
"data": {
"totp_url": "otpauth://totp/Advantch:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Advantch",
"secret": "JBSWY3DPEHPK3PXP"
}
}Step 2: Activate TOTP
User scans QR code, then submits verification code:
POST /_allauth/browser/v1/account/authenticators/totp
Content-Type: application/json
{
"code": "123456"
}Frontend Implementation
const { setupTotp, activateTotp } = useAuth();
// Get setup data (secret + QR URL)
const { totp_url, secret } = await setupTotp();
// After user scans QR and enters code
await activateTotp(code);MFA During Login
When MFA is enabled, login returns a pending flow:
{
"data": {
"flows": [
{ "id": "mfa_authenticate", "is_pending": true }
]
}
}Complete MFA
POST /_allauth/browser/v1/auth/2fa/authenticate
Content-Type: application/json
{
"code": "123456"
}Frontend Implementation
const { login, pendingFlow, authenticateMfa } = useAuth();
await login(email, password);
if (pendingFlow?.id === "mfa_authenticate") {
router.push("/mfa/verify");
}
// On MFA page
await authenticateMfa(code);WebAuthn / Passkeys
Register Authenticator
POST /_allauth/browser/v1/account/authenticators/webauthn
Content-Type: application/json
{
"name": "My Security Key",
"credential": { /* WebAuthn credential response */ }
}Authenticate
POST /_allauth/browser/v1/auth/webauthn/authenticate
Content-Type: application/json
{
"credential": { /* WebAuthn assertion response */ }
}Recovery Codes
Generate Codes
POST /_allauth/browser/v1/account/authenticators/recovery-codesResponse:
{
"data": {
"codes": [
"abcd-1234",
"efgh-5678",
"ijkl-9012",
...
]
}
}Use Recovery Code
POST /_allauth/browser/v1/auth/2fa/authenticate
Content-Type: application/json
{
"code": "abcd-1234"
}Frontend Routes
| Route | Component | Purpose |
|---|---|---|
/mfa/verify | MFA verification form | Enter TOTP code after login |
/mfa/setup | MFA setup wizard | Configure TOTP/WebAuthn |
/settings/security | Security settings | Manage authenticators |
Testing MFA
@pytest.mark.django_db
def test_mfa_required_after_login(client, user_with_mfa):
response = client.post(
"/_allauth/browser/v1/auth/login",
data={"email": user.email, "password": TEST_PASSWORD},
content_type="application/json",
)
assert response.status_code == 200
data = response.json()
assert any(f["id"] == "mfa_authenticate" for f in data["data"]["flows"])Security Best Practices
- Require MFA for sensitive operations: Password changes, email changes, account deletion
- Rate limit MFA attempts: Prevent brute force attacks
- Recovery codes: Always generate and securely store backup codes
- Session tracking: Monitor active sessions with
USERSESSIONS_TRACK_ACTIVITY
Last updated on