HyperSaaS
FrontendAuthentication

Two-Factor Authentication

The sign-in code step, the Security settings cards, recovery codes and confirming it's you.

Two-factor authentication is optional. People turn it on in Settings → Security, and from then on, signing in asks for a code from their authenticator app after the password. The backend side is described in Two-Factor Authentication.

Signing In

UserLoginForm (components/auth/user-login-form.tsx) has two steps. When loginAction answers step: "code", the form shows a code field (6 digits, autocomplete="one-time-code", so phones can fill it in), with Use a recovery code instead and Back to sign in. Between the two steps, allauth's session token waits in an httpOnly cookie for five minutes; if it runs out, the form asks for the password again.

When the person chose sign-in codes by email instead, loginAction answers step: "email-code", and the same step reads Check your email. The code goes to allauth's auth/code/confirm (verifyCodeAction(code, "email-code")). After three wrong codes, the form goes back to the password, which sends a new code.

The Security Card

TwoFactorCard (components/settings/two-factor/two-factor-card.tsx) sits between the password and account deletion.

Off: Set up opens a dialog with a QR code (qrcode.react) and the key to type in by hand. A code from the app turns it on, and the recovery codes appear, once, with Copy and Download.

On: a badge, the number of recovery codes left, New recovery codes (the old ones stop working) and Turn off.

Its API routes call allauth as the signed-in user, through relayToAllauth:

RouteDoes
GET /api/account/two-factorThe methods turned on, and recovery codes left
DELETE /api/account/two-factorTurns it off
GET /api/account/two-factor/setupA secret and the QR code's URL
POST /api/account/two-factor/setupTurns it on with a code; answers with the recovery codes
POST /api/account/two-factor/recovery-codesNew recovery codes
POST /api/account/reauthenticateConfirms it's you, with { password } or { code }

relayToAllauth answers in the shape callApi reads ({ data }, or { detail } on failure), and never passes on a token from allauth's answers.

Sign-in Codes by Email

EmailCodeCard (components/settings/two-factor/email-code-card.tsx) sits under the two-factor card: Turn on, or Turn off, which asks for the password. It says when the authenticator app is used instead, and hides itself when the backend doesn't offer the option. Its route, /api/account/email-code (GET, PUT), relays to the backend's /auth/sign-in-email-code/.

Confirming It's You

Changes to two-factor settings need a sign-in in the last five minutes. When allauth wants the password or a code again, the routes answer 403 with reauth, saying which it accepts. useReauthenticate() (components/settings/two-factor/reauthenticate.tsx) handles it:

const { withReauth, dialog } = useReauthenticate();

const result = await withReauth(() => callApi("/api/account/two-factor", { method: "DELETE" }));
// If allauth asked, a dialog took the password (or a code) and the call was made again.

return <>{/* ... */}{dialog}</>;

On this page