Two-factor authentication with TOTP and recovery codes, through allauth's mfa app #27

Closed
opened 2026-09-05 13:47:37 +00:00 by tiagoagueda · 0 comments
Owner

Observation

also add the possibility of adding TOTP

What exists today

  • Sign-in is a password and nothing else. INSTALLED_APPS enables allauth and allauth.account.
  • The capability is already in the box. django-allauth 65.19.2 ships allauth.mfa with three authenticator types — totp, recovery_codes and webauthn — plus "trust this browser" and the reauthentication step that guards changes to any of them. Its settings, checked in the installed package: MFA_SUPPORTED_TYPES, MFA_TOTP_ISSUER, MFA_TOTP_DIGITS, MFA_TOTP_PERIOD, MFA_TOTP_TOLERANCE, MFA_RECOVERY_CODE_COUNT, MFA_RECOVERY_CODES_SHOW_ONCE, MFA_TRUST_ENABLED, MFA_TRUST_COOKIE_AGE, MFA_PASSKEY_LOGIN_ENABLED, MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN, MFA_ADAPTER.
  • Two small dependencies are missing. allauth's mfa extra pulls qrcode (for the enrolment QR code) and fido2 (passkeys only). Neither is installed; pyproject.toml depends on django-allauth with no extras.
  • No templates/mfa/ overrides exist; the account/ ones show the pattern to follow.

So this is enabling and styling what Postulo already depends on, plus one dependency line. Not a plugin: signing in is core, and it must work with nothing extra installed.

Shape

1. Enable allauth.mfa with MFA_SUPPORTED_TYPES = ["totp", "recovery_codes"] and django-allauth[mfa] in pyproject.toml. qrcode renders the code as SVG; no image library involved. MFA_TOTP_ISSUER is the instance name once #24 has one, "Postulo" until then, so the entry in the authenticator app is recognisable.

2. Where it lives: Settings → Account → Two-factor authentication (#22). Activate: scan the QR code or type the key, confirm with one code, then the recovery codes shown once (MFA_RECOVERY_CODES_SHOW_ONCE), with Download and I have saved these. Afterwards: regenerate codes, see how many remain, deactivate. Every change asks for the password again first — allauth's reauthentication, which is the right friction.

3. Signing in. Password, then the code, with trust this browser for 30 days (MFA_TRUST_ENABLED, MFA_TRUST_COOKIE_AGE) so a personal machine is not asked daily. The same second step applies after an SSO sign-in (#6): allauth runs MFA on any login, which is what one wants — the identity provider's assurance is not Postulo's to assume.

4. What it does not protect, said plainly. Capture tokens and the personal access tokens of #12 are their own credential and bypass the second factor by design — an extension or an agent has no code to type. The wiki says so, and the tokens page says so, so nobody believes TOTP covers the API.

5. Recovery on a self-hosted instance. There is no support desk. Two paths, both needed: the recovery codes, and an administrator resetting a person's second factor from Server settings → People (#24) after satisfying themselves who is asking — plus a management command for the administrator who locks themselves out, manage.py mfa_reset <email>, so recovery never requires editing the database by hand.

6. Policy. A Server settings switch (#24 → Sign-in): require two-factor for administrators and require it for everyone. allauth has no such policy built in; a small middleware that sends a signed-in person without an authenticator to the activation page, once the switch is on, is all it takes. Default off; administrators-only is the sensible first setting on a shared instance.

7. Templates. allauth's mfa/ templates restyled into the shell exactly as the account/ ones were, including the sign-in code prompt. The QR code is inline SVG, so the CSP is untouched.

8. Stage two: passkeys. Same app, webauthn in MFA_SUPPORTED_TYPES, fido2 installed. Passkeys are the better factor and can even replace the password (MFA_PASSKEY_LOGIN_ENABLED), but WebAuthn requires a secure origin: the ragnar test instance on plain HTTP inside the mesh could not use them (MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN exists for development only). TOTP first, passkeys as their own step once the shape is in place.

9. Documentation. Accounts and invitations gains a two-factor section; Troubleshooting gains "I lost my phone".

Classification

Enhancement. Not breaking: opt-in per person, the policy switch is off by default, and the only operator-visible change is one dependency that the image already carries once merged.

Open questions

  1. Trust-this-browser duration: 30 days, or configurable per instance from #24?
  2. Should activating TOTP be suggested to administrators on first sign-in even when not required? A banner, dismissable once.
  3. Recovery codes: allauth's default count and digits, or fewer, longer codes? Defaults, unless someone has a reason.
## Observation > also add the possibility of adding TOTP ## What exists today - Sign-in is a password and nothing else. `INSTALLED_APPS` enables `allauth` and `allauth.account`. - **The capability is already in the box.** django-allauth 65.19.2 ships `allauth.mfa` with three authenticator types — `totp`, `recovery_codes` and `webauthn` — plus "trust this browser" and the reauthentication step that guards changes to any of them. Its settings, checked in the installed package: `MFA_SUPPORTED_TYPES`, `MFA_TOTP_ISSUER`, `MFA_TOTP_DIGITS`, `MFA_TOTP_PERIOD`, `MFA_TOTP_TOLERANCE`, `MFA_RECOVERY_CODE_COUNT`, `MFA_RECOVERY_CODES_SHOW_ONCE`, `MFA_TRUST_ENABLED`, `MFA_TRUST_COOKIE_AGE`, `MFA_PASSKEY_LOGIN_ENABLED`, `MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN`, `MFA_ADAPTER`. - **Two small dependencies are missing.** allauth's `mfa` extra pulls `qrcode` (for the enrolment QR code) and `fido2` (passkeys only). Neither is installed; `pyproject.toml` depends on `django-allauth` with no extras. - No `templates/mfa/` overrides exist; the `account/` ones show the pattern to follow. So this is enabling and styling what Postulo already depends on, plus one dependency line. Not a plugin: signing in is core, and it must work with nothing extra installed. ## Shape **1. Enable `allauth.mfa`** with `MFA_SUPPORTED_TYPES = ["totp", "recovery_codes"]` and `django-allauth[mfa]` in `pyproject.toml`. `qrcode` renders the code as SVG; no image library involved. `MFA_TOTP_ISSUER` is the instance name once #24 has one, "Postulo" until then, so the entry in the authenticator app is recognisable. **2. Where it lives: Settings → Account → Two-factor authentication** (#22). Activate: scan the QR code or type the key, confirm with one code, then the **recovery codes shown once** (`MFA_RECOVERY_CODES_SHOW_ONCE`), with *Download* and *I have saved these*. Afterwards: regenerate codes, see how many remain, deactivate. Every change asks for the password again first — allauth's reauthentication, which is the right friction. **3. Signing in.** Password, then the code, with **trust this browser for 30 days** (`MFA_TRUST_ENABLED`, `MFA_TRUST_COOKIE_AGE`) so a personal machine is not asked daily. The same second step applies after an SSO sign-in (#6): allauth runs MFA on any login, which is what one wants — the identity provider's assurance is not Postulo's to assume. **4. What it does not protect, said plainly.** Capture tokens and the personal access tokens of #12 are their own credential and bypass the second factor by design — an extension or an agent has no code to type. The wiki says so, and the tokens page says so, so nobody believes TOTP covers the API. **5. Recovery on a self-hosted instance.** There is no support desk. Two paths, both needed: the recovery codes, and **an administrator resetting a person's second factor** from Server settings → People (#24) after satisfying themselves who is asking — plus a management command for the administrator who locks *themselves* out, `manage.py mfa_reset <email>`, so recovery never requires editing the database by hand. **6. Policy.** A Server settings switch (#24 → Sign-in): *require two-factor for administrators* and *require it for everyone*. allauth has no such policy built in; a small middleware that sends a signed-in person without an authenticator to the activation page, once the switch is on, is all it takes. Default off; administrators-only is the sensible first setting on a shared instance. **7. Templates.** allauth's `mfa/` templates restyled into the shell exactly as the `account/` ones were, including the sign-in code prompt. The QR code is inline SVG, so the CSP is untouched. **8. Stage two: passkeys.** Same app, `webauthn` in `MFA_SUPPORTED_TYPES`, `fido2` installed. Passkeys are the better factor and can even replace the password (`MFA_PASSKEY_LOGIN_ENABLED`), but WebAuthn requires a secure origin: the ragnar test instance on plain HTTP inside the mesh could not use them (`MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN` exists for development only). TOTP first, passkeys as their own step once the shape is in place. **9. Documentation.** *Accounts and invitations* gains a two-factor section; *Troubleshooting* gains "I lost my phone". ## Classification Enhancement. Not breaking: opt-in per person, the policy switch is off by default, and the only operator-visible change is one dependency that the image already carries once merged. ## Open questions 1. Trust-this-browser duration: 30 days, or configurable per instance from #24? 2. Should activating TOTP be *suggested* to administrators on first sign-in even when not required? A banner, dismissable once. 3. Recovery codes: allauth's default count and digits, or fewer, longer codes? Defaults, unless someone has a reason.
tiagoagueda added this to the 0.2.0 milestone 2026-09-05 13:47:37 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
Postulo/postulo#27
No description provided.