Two-factor authentication during sign-in

M7 consumer accounts can link an authenticator and choose where a code is required in two-factor settings. Enrollment supports a QR code or manual setup key for a standard TOTP app using SHA-1, six digits, and a 30-second period.

Applications continue to use authorization code with PKCE or Device authorization. M7 hosts the password, authenticator, and recovery screens. Your application must not collect an M7 authenticator secret, TOTP code, or recovery code, or submit those values to the OAuth token endpoint.

Where checks apply

The user controls four independent settings after linking and verifying an authenticator:

Setting When M7 asks for a code
Password logins After a correct M7 password during regular browser authorization, before completing that password sign-in.
Email sign-in After successful magic-link or email-code proof during consumer browser authorization.
Device Code After a correct M7 password during Device Code approval, before completing approval.
Account switches and existing-session sign-ins When selecting a remembered consumer account or using an account already open in the browser to authorize an application, subject to the selected interval.

These settings apply to consumer accounts. The password and Device Code settings do not depend on each other. Email sign-in has its own setting, which defaults to off and applies to both email methods. See email sign-in for browser binding, confirmation, and expiry.

A fresh federated sign-in uses the provider's authentication policy and does not add the local-password TOTP step. Later reuse of that remembered account follows M7's account/session setting, even when the account originally signed in through federation.

Refresh-token requests and machine client_credentials issuance do not prompt for these codes. Refreshing a token does not restart the account-verification freshness interval. Linking an authenticator, or enabling one setting, does not mean every login path is subject to the same check.

Account switches and existing sessions

The interval choices are Every time, 15 minutes, one hour, and eight hours. A missing or expired verification requires a new code. Within a nonzero interval, recent successful verification for the same account in the same browser session can satisfy the check. Another account, another browser, or a replacement authenticator does not inherit that proof.

Every time means one proof for each new protected authorization transaction, not another prompt on every page within the same transaction. Selecting an account and continuing its authorization should not require duplicate codes. Using an existing M7 login to enter a different application is also an authorization transaction; it is not a refresh-token request.

The verification interval is an M7 account setting. It is separate from the OIDC max_age parameter and does not create a new token lifetime or refresh policy.

What an application receives

On success, the normal authorization callback and code exchange remain unchanged. Validate state, use the transaction's PKCE verifier, and follow the existing callback contract. Do not infer an amr or acr guarantee from the fact that an M7 account has enabled one of these settings.

For prompt=none, an existing session can still require a code. When that account-verification step is due and callback trust has been established, M7 returns error=login_required with error_reason=account_two_factor_required to the registered callback. Validate state and start a new interactive authorization without prompt=none when the user chooses to continue. Do not repeatedly retry the silent request.

During Device Code approval, token pickup remains pending until the required sign-in, two-factor proof, and approval complete. Continue respecting the returned polling interval and the existing authorization_pending, slow_down, denial, and expiry behavior. The application never receives the user's TOTP or recovery code.

An invalid or expired code does not complete sign-in. M7 applies proof-attempt limits and may require restarting the flow. A successful TOTP time step is not reusable; wait for the app's next code before another successful proof.

Recovery codes

After linking an authenticator, the user can generate ten recovery codes in M7 settings by confirming with a current authenticator code. The codes are shown once. Generating another set invalidates the entire previous set.

On a hosted two-factor prompt, Use a recovery code accepts one unused code in place of the authenticator code. It remains part of the ongoing sign-in: it is not a standalone password replacement or an OAuth credential.

M7's recovery screen checks Disable two-factor authentication by default:

  • With that option selected, successful recovery fully unlinks the authenticator, turns all four checks off, invalidates its remaining recovery codes and pending challenges, and lets the current sign-in continue.
  • With the option cleared and more than one code remaining, M7 asks for confirmation, consumes one code on success, and keeps the authenticator and settings. The user needs another unused recovery code or the authenticator to unlink later.
  • Using the final recovery code always performs the full unlink, regardless of the checkbox. The server uses the current remaining count, so an older screen cannot override this rule.

Invalid or already used recovery codes do not establish proof. After recovery has unlinked the authenticator, visit settings to enroll the replacement, choose the desired checks, and generate a fresh recovery set.

Unlinking when the authenticator is unavailable

The settings page offers unlink proof using a current authenticator code, an unused recovery code, or an eligible existing linked GitHub or Discord identity. For linked-account proof, M7 takes the user through the selected provider and returns to settings for an explicit final confirmation. The identity must match the already linked account; cancellation does not unlink the authenticator.

Successful unlink turns all checks off and invalidates the authenticator's recovery codes and pending challenges. It leaves the identity provider linked to the M7 account. Provider proof may reuse the provider's session; it does not guarantee a fresh provider password prompt or provider-side MFA.

For account-management integrations, see the API User two-factor contract. Treat setup keys and recovery codes as secrets. Keep them out of application logs and URLs, and store recovery codes separately from the authenticator.