examples/totp-stepup/main.go.
Configure
Issuer is the label shown in the authenticator app. Recovery codes are 10 hex characters from crypto/rand.
Routes
a.Mount(r) adds these under the auth prefix (/auth by default) when Config.TOTP is set:
Enrollment, verify, recovery, and regeneration are rate limited per IP. Regenerating returns 409 if the user is not enrolled.
Enrollment
- Call
enroll/begin. RenderotpAuthUrlas a QR code, or showsecretfor manual entry. The library does not generate QR images. - The user types the current code. Send it to
enroll/finishwith theenrollmentId. - Show the returned
recoveryCodesonce and ask the user to store them.
BeginTOTPEnrollment(ctx, userID, accountName) and FinishTOTPEnrollment(ctx, userID, enrollmentID, code).
Sign-in with a second factor
Once a user is enrolled,POST /auth/email-password/signin no longer returns a full session. It responds {"step":"totp_required"} and sets a pending session cookie. In Go code this is SigninStepTOTPRequired; a complete sign-in is SigninStepFull.
The client then posts the 6-digit code to /auth/totp/verify. On success the pending session is replaced with a full one. A lost device can use /auth/totp/recovery with a recovery code, which is single use.
Programmatic equivalents are VerifyTOTP(ctx, pendingSessionToken, code) and the recovery counterpart on TheAuth.
Step-up for sensitive routes
RequireRecentAuth(maxAge) is chi-compatible middleware. It requires a session and returns 403 auth.recent_auth_required unless the user signed in or stepped up within maxAge.
/auth/step-up:
password, totp, and passkey (StepUpMethodPassword, StepUpMethodTOTP, StepUpMethodPasskey). A success elevates the session for Config.StepUpTTL (default 5 minutes). Step-up needs a storage that implements SessionManagementStorage; otherwise the route answers 501.
Security notes
- Each accepted code is single use: a code from a time step that was already consumed is rejected, which blocks replay inside the 30 second window.
- Wrong codes count against a per-user MFA lockout (
LoginThrottleConfig.MFAMaxFailures, default 5, forMFALockout, default 15 minutes) and against the pending session, which is revoked after repeated failures. - Recovery codes are as strong as the second factor. Tell users to store them offline.
- Keep
EncryptionKeyin a secrets manager. Losing it makes enrolled secrets unreadable.