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

MethodStatusDescription
TOTP✅ EnabledTime-based One-Time Password (Google Authenticator, etc.)
WebAuthn✅ EnabledHardware keys and biometrics (YubiKey, Touch ID, etc.)
Recovery Codes✅ EnabledBackup codes for account recovery
Passkey Login✅ EnabledPasswordless 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 = True

TOTP Setup

Step 1: Get TOTP Secret

GET /_allauth/browser/v1/account/authenticators/totp

Response:

{
  "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-codes

Response:

{
  "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

RouteComponentPurpose
/mfa/verifyMFA verification formEnter TOTP code after login
/mfa/setupMFA setup wizardConfigure TOTP/WebAuthn
/settings/securitySecurity settingsManage 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

  1. Require MFA for sensitive operations: Password changes, email changes, account deletion
  2. Rate limit MFA attempts: Prevent brute force attacks
  3. Recovery codes: Always generate and securely store backup codes
  4. Session tracking: Monitor active sessions with USERSESSIONS_TRACK_ACTIVITY

Last updated on

On this page