# Second factor

> Setting up a required second factor and proving it at sign-in with a code, passkey or recovery code, unless a remembered browser or a trusted zone spares it.

A user who holds a second factor proves it after the first factor: a password, a
[social sign-in](https://www.nauthera.io/docs/v1/sign-in-flows/social-sign-in) or a passkey that did not verify
its user. Where the policy requires a factor
([Multi-Factor Authentication](https://www.nauthera.io/docs/v1/mfa#the-policy)), a user who has none is
enrolled before the sign-in completes, at sign-in and at sign-up. Both screens resume the
sign-in in progress from its cookie, and the flow ends at the application that asked.

The policy is resolved for every sign-in from the deployment, the organization and the
client, and its rules can tighten it for this sign-in: a sign-in from outside a named
network zone, from a new network or from a new browser may have to answer with a second
factor, or with a passkey only, where another would not.

## Set up a second factor

```mermaid
flowchart LR
  P[Password or sign-up] --> C{More than one method?}
  C -->|yes| CH[Choose a method]
  C -->|no| ONE[That method's setup]
  CH --> T[Authenticator app]
  CH --> K[Passkey]
  CH --> EM[Emailed code]
  T --> RC[Recovery codes]
  K --> RC
  EM --> RC
  RC -->|I have saved them| CB([Back to the application])
```

When more than one method is allowed and the browser can use them, the user chooses.
The chooser stays reachable from each method, so a user whose device refuses a passkey can
set up a code instead.

<StoryScreenshot id="screens-mfa-method-chooser--default" alt="Set up two-step verification: choose a passkey or an authenticator app" />

**Authenticator app.** The page shows *Preparing your setup…* while it fetches the secret,
then a QR code with the key to type by hand. A wrong code is refused on the page, and the
current code still works after it.

<StoryScreenshot id="screens-mfa-totp-setup--loading" alt="Preparing your setup…" />
<StoryScreenshot id="screens-mfa-totp-setup--default" alt="Set up authenticator app: QR code, manual key and the code to confirm" />
<StoryScreenshot id="screens-mfa-totp-setup--with-error" alt="Setup failed: that code is incorrect or has expired" />

**Emailed code.** Offered only where the policy names `email`: the deployment's
`mfa.methods` leaves it out by default, and the chooser marks it as less secure than the
others. Opening it mails a 6-digit code to the account's address; entering it activates
the factor. A code works for 10 minutes, another can be asked for after 30 seconds (the
button counts down), and an account is sent at most five in 30 minutes. It needs an
[email relay](https://www.nauthera.io/docs/v1/email) for the organization.

<StoryScreenshot id="screens-mfa-method-chooser--with-email" alt="The method chooser with Email offered last, marked less secure" />

**Recovery codes.** The account's first factor comes with single-use recovery codes, ten
by default (`mfa.recovery_code_count`), shown once. **Continue** opens only after the user
ticks that they saved them. If the acknowledgement is throttled or the sign-in lapsed, the
codes stay on the screen with the reason, so none is lost.

<StoryScreenshot id="screens-mfa-recovery-codes--default" alt="Save your recovery codes, with the checkbox and Continue" />
<StoryScreenshot id="screens-mfa-recovery-codes--rate-limited" alt="Recovery codes still on screen with a throttle message" />

Opening `/mfa/setup` with no sign-in behind it says the setup could not start, with
**Sign in**.

<StoryScreenshot id="screens-mfa-totp-setup-unavailable--default" alt="Couldn't start setup, with Sign in" />

A user whose only factor the policy no longer permits, for example a TOTP user under a
rule that allows only passkeys, is sent here to set up a permitted one.

For setting up a passkey, see [Passkeys](https://www.nauthera.io/docs/v1/sign-in-flows/passkeys).

## Prove it at sign-in

```mermaid
flowchart LR
  P[Password] --> M["/mfa"]
  M -->|6-digit code, emailed code or passkey| CB([Back to the application])
  M -->|6-digit code, passwordless open, no passkey| O[Offer of a passkey]
  O --> CB
  M -->|Use a recovery code instead| R["/mfa/recovery"]
  R --> CB
  M -->|too many wrong codes| T[Too many requests]
  M -->|sign-in lapsed| E[Your sign-in session expired]
  E -->|Sign in again| L["/login"]
```

<StoryScreenshot id="screens-mfa-authenticator-code--default" alt="Enter the 6-digit code from your authenticator app" />
<StoryScreenshot id="screens-mfa-authenticator-code--invalid-code" alt="That code is incorrect or has expired" />
<StoryScreenshot id="screens-mfa-authenticator-code--with-resend" alt="Enter the 6-digit code we sent to your address, with Resend code" />
<StoryScreenshot id="screens-mfa-recovery--default" alt="Use a recovery code" />

- `/mfa` opens on the first method the server offers, with links to the user's other
  methods: a user who holds both a code and a passkey can switch between them.
- A user whose factor is an emailed code is sent one as `/mfa` opens, with **Resend
  code** once the wait is over.
- A wrong code is a banner; the user tries again.
- **Use a recovery code instead** opens `/mfa/recovery`. Each code works once.
- Too many wrong codes or refused passkeys lock the factor for `mfa.lock_duration`: the
  throttle screen, with the wait. Recovery guessing locks the recovery path the same way.
- A sign-in that waited too long at this step has lapsed: *Your sign-in session expired*,
  with **Sign in again**, which resumes the application's request.
- A user whose passkey is their factor sees the passkey challenge instead, with a link to
  a code when they also hold one.
- Where passwordless sign-in is open and the user holds no passkey, a correct code leads
  to the [offer of a passkey](https://www.nauthera.io/docs/v1/sign-in-flows/passkeys#the-offer-after-sign-in)
  before the application.

<StoryScreenshot id="screens-error-pages-session-expired--default" alt="Your sign-in session expired, with Sign in again" />

## When the challenge is spared

A user who holds a permitted factor goes straight back to the application, with no
`/mfa` step, when the policy spares this sign-in:

- **A remembered browser.** The browser passed a second factor for this user within the
  window the policy remembers browsers for, and the password and factors have not changed
  since. Browsers are remembered without a prompt; a policy with no window remembers none.
- **A trusted zone.** The sign-in comes from a network zone the deployment declared and
  the policy spares.
- **A sparing identity provider.** The sign-in came through an identity provider set to
  `spareSecondFactor`, which did not vouch for a second factor itself
  ([Social sign-in](https://www.nauthera.io/docs/v1/sign-in-flows/social-sign-in#the-providers-second-factor)).
  This one overrides the policy: it spares enrolment too, and applies under a `require`
  rule.

Otherwise, a rule with `require`, or a policy that always challenges, spares nothing, and
enrolment is never spared. A spared sign-in is single-factor, and its tokens carry no `mfa` in `amr`.
The details are in [Multi-Factor Authentication](https://www.nauthera.io/docs/v1/mfa#sparing-the-challenge).

## After a factor changes

Activating an authenticator app or an emailed code ends every other sign-in the user has in progress, and
every session and refresh grant they hold elsewhere. A browser remembered under the old
factors is asked again.
