Skip to content

Multi-factor authentication (MFA)

This page covers MFA policy and administration for administrators and server access. How an individual account sets up MFA, signs in with it, or uses recovery codes is in the user guide: Multi-factor authentication.

byro supports multi-factor authentication for all users of the backend ("office") based on time-based one-time passwords (TOTP, RFC 6238). Any common authenticator app works, for example Aegis, Google Authenticator, Microsoft Authenticator, 1Password or Bitwarden.

MFA is optional by default: every backend user can enable it for their own account. Superusers can additionally require MFA for every user who can log in to the backend, with a separate option to trust MFA enforced by the OIDC identity provider.

MFA only concerns the interactive backend login. It does not change

  • the member pages: members keep using their personal links, no login or second factor is needed there,
  • the REST API: API tokens keep working, regardless of the MFA state of the token's user or of the global policy. API requests are never redirected to an MFA page.

Note

The MFA policy applies to every account that can log in to the office: staff accounts as well as superusers (see Permission model). It is not limited to superusers. Changing the policy is part of the general settings and therefore reserved for superusers.

Choosing an MFA policy

Under Settings → General you find the card Multi-factor authentication with these policies:

  • Optional (the default): users can choose to set up MFA themselves.
  • Required for all backend users: every backend login must complete byro's TOTP setup and challenge.
  • Required for all backend users except OIDC logins: password logins follow the same requirement, while a session authenticated through OIDC is not sent to byro's MFA setup when the user has no personal authenticator.

The required policies have the following effects:

  • Users who already use MFA are not affected; they can no longer disable it, though.
  • Users without MFA are sent to the MFA setup right after entering their password. Until the setup is complete, they cannot use any other page of the backend (only the setup itself and logout).
  • Sessions that were already logged in without MFA are treated the same way: the next request is redirected to the setup.
  • The first required policy applies to OIDC logins as well. The OIDC-exception policy deliberately trusts the identity provider to enforce MFA; byro does not evaluate its acr or amr claims.
  • A personal byro authenticator is never exempt. A user who set up a TOTP device must complete its challenge after both password and OIDC logins.
  • Enabling the option does not require that everybody has set up MFA already. Nobody is locked out – but every user has to enroll at their next login.

Changing the policy is recorded in the audit log.

Display in authenticator apps

Entries created from byro's QR code always show BYRO as the service name. The second line, the account name, is configurable in the same settings card (Account name in authenticator apps). The default is {association} - {username}, i.e. the association name from the general settings followed by the username. Available placeholders: {username}, {email}, {name} (the user's name) and {association}, for example {name} ({email}).

Colons are not allowed: the otpauth format used by authenticator apps separates the service name from the account with a colon.

The setting only affects newly set up authenticators; existing entries in the users' apps keep the name they had when they were created.

Checking a user's MFA status

The user list marks users with MFA with a shield icon. On the server:

$ python manage.py mfa_status <username or e-mail>

prints something like:

User: admin (admin@example.org)
Active: yes
MFA enabled: yes
TOTP device: configured (since Sept. 1, 2026, 10:00 a.m., last used Sept. 4, 2026, 9:12 a.m.)
Recovery codes remaining: 6
MFA required by policy: no

Secrets and recovery codes are never displayed.

Resetting the MFA of a user (break-glass recovery)

If a user lost their authenticator and has no recovery codes left (or is locked out for any other reason), reset their MFA on the server:

$ python manage.py mfa_reset <username or e-mail>

The command shows a warning and asks you to type the username to confirm. It then

  1. removes the authenticator (TOTP secret) and all recovery codes,
  2. terminates all existing sessions of that user,
  3. writes an audit log entry (byro.mfa.reset).

For scripted recovery, --force skips the confirmation prompt.

Warning

A reset is a recovery mechanism, not a way around the policy. If MFA is required for the user's login method, the reset only allows the user to enroll again: after the next password login they are sent to the MFA setup and have to configure a new authenticator before they can use the backend. The global policy is not changed by the command.

Sessions can only be terminated automatically with the default database-backed session storage (SESSION_ENGINE ending in .db); the command tells you if that is not the case.

Security notes

  • TOTP secrets are stored encrypted. The encryption key is derived from Django's SECRET_KEY (see Configuration). Keep the secret key stable and back it up together with the database (see Backup and restore): if it is lost, no user can pass the MFA step any more and every account has to be reset with mfa_reset. byro offers no configuration option to list a previous secret key as a fallback; changing the secret key is therefore equivalent to losing all existing sessions and MFA devices, not a seamless rotation.
  • Recovery codes are stored as password hashes and are single use.
  • Brute force protection: after every failed code, the account's MFA is locked for an exponentially growing time (1, 2, 4, 8, … seconds), for authenticator codes and recovery codes alike. Each TOTP code is accepted only once. byro itself does not rate limit the password login; as before, we recommend rate limiting on the reverse proxy (for example nginx limit_req for /login/) or fail2ban on the web server logs.
  • Audit log: enabling (byro.mfa.enabled), disabling (byro.mfa.disabled) and resetting (byro.mfa.reset) MFA, generating new recovery codes (byro.mfa.recovery_codes.regenerated) and signing in with a recovery code (byro.mfa.recovery_code.used) are recorded in the audit log. Secrets, codes and QR codes are never logged; failed attempts are written to the application log only.
  • System time: TOTP depends on a correct clock on the server (and on the user's phone). Run an NTP client on the server.

Notes for plugin developers

Every URL that requires a login is automatically covered by the MFA enforcement, plugin views included. URLs that a plugin marks as public via the unauthenticated_urls signal are exempt from MFA as well. In views, request.user.is_verified() tells whether the current session has passed the MFA step (it is always True for users who do not need MFA once they are past the middleware).