Two-factor authentication
A password on its own protects an account only as long as nobody else learns it. Two-factor authentication adds a second proof at sign-in, so that a stolen password is not enough to take over the account. The second proof is a one-time code, generated by an authenticator application on the user’s phone or computer.
Stalwart implements time-based one-time passwords (TOTP), as defined in RFC 6238. The server and the authenticator application share a secret. Every period, normally 30 seconds, the application derives a new code from the secret and the current time. The server performs the same computation and accepts the code when the results match.
Two-factor authentication is available only for accounts of the internal directory, whose passwords are stored on the server. Accounts of an LDAP or SQL directory are never asked for a code, and a TOTP URL stored on such an account is ignored at sign-in. Two-factor authentication is also enabled account by account: no setting requires it for every account of a server, domain or tenant.
Signing in with two-factor authentication
Section titled “Signing in with two-factor authentication”The one-time code can be entered in only one place: the sign-in form of the OAuth server, which the WebUI and every OAuth application use.
Password sign-ins over IMAP, POP3, SMTP, ManageSieve and HTTP Basic authentication have no place for a code. For an account with two-factor authentication they therefore always fail, even with the correct password. This is the most common consequence of enabling two-factor authentication: a mail client that stored the account’s password stops working. Such a client has two ways forward:
- Sign in with OAuth, through the
OAUTHBEARERorXOAUTH2mechanism (Mail, calendar and contacts clients). - Use an app password, which is not subject to two-factor authentication.
An OAuth application asks for the code only when the user signs in. After that, it renews its access with its refresh token, without a new code, until the grant ends (Tokens).
When a password sign-in fails because the code is missing, the server produces an auth.mfa-required event. On SMTP, the reply is 334 5.7.8 This account requires multi-factor authentication. Alternatively, you can use an app password if your account has one..
The same limitation affects master-user sign-ins, in which one account signs in on behalf of another. Because these protocols cannot carry a code, an account with two-factor authentication cannot act as the impersonating account over them.
Enable two-factor authentication
Section titled “Enable two-factor authentication”Users enable two-factor authentication themselves in the Account Manager, under Account › Credentials › Password. The page generates a new secret and shows it as a QR code. The user scans the code with an authenticator application, and the page then asks for the code the application shows, to confirm that the setup works.
Changing or removing two-factor authentication later also requires a current code. This prevents someone who has only the password from turning the protection off. The AccountPassword singleton carries that code in otpAuth.otpCode, next to the URL in otpAuth.otpUrl.
An administrator can also set or remove two-factor authentication on any account. The setting is the otpUrl field of the account’s Password credential in credentials. The URL contains the shared secret, so after setting it the administrator must pass it to the user, usually as a QR code. The following update sets a URL on the account c, whose password is its first credential:
{ "credentials/0/otpUrl": "otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=Example"}Changing or clearing otpUrl does not sign out OAuth applications, whose existing grants keep working.
Accepted otpauth URLs
Section titled “Accepted otpauth URLs”The shared secret and its parameters travel as an otpauth://totp/ URL, the format that authenticator applications read from a QR code. The server accepts such a URL under these rules:
- The scheme and type must be
otpauth://totp/, sohotpURLs and Steam URLs are not accepted. secretis the base32-encoded shared secret and must be at least 128 bits long (26 base32 characters).algorithmisSHA1,SHA256orSHA512, without regard to case. It isSHA1when the parameter is absent.digitsis 6 to 8, and 6 when absent.periodis any number of seconds other than zero, and 30 when absent.- The label is
<issuer>:<account name>, and neither part may contain:. When the URL also has anissuerparameter, it must equal the issuer in the label.
The server accepts codes for the previous, the current and the next period. This tolerates a clock difference of one period between the server and the authenticator, but no more, so the server’s clock must be accurate.
The parameters should stay at SHA1, 6 digits and a 30-second period unless every authenticator in use is known to support other values. Many authenticator applications ignore the parameters and generate SHA-1, 6-digit, 30-second codes regardless, and those codes would then never match.
The server does not validate the URL when it is stored. A URL that cannot be parsed shows its effect only at the next sign-in that supplies a code, which then fails with an auth.error event. To avoid this, set a new URL from the Account Manager, which confirms it with a code, or test the sign-in after an administrator sets one.
Recover access
Section titled “Recover access”A user who has lost the authenticator is locked out: without a code, the user can neither sign in to the WebUI nor remove two-factor authentication. An administrator restores access by clearing otpUrl on the user’s Password credential:
{ "credentials/0/otpUrl": null}The user then signs in with the password alone and enables two-factor authentication again, with a new secret. App passwords keep working throughout, so a user who has one can continue to use mail clients while access is restored.