# Sign in with an email and password

> Signing in with an email address and password at /login: validation, wrong passwords, captchas, throttles and lockout, required password changes and SSO.

An application sends the user to `/authorize`. Without a session the server hands off to
`/login`, keeping the authorization request in the query. A successful sign-in goes back
through `/authorize`, which redirects to the application's `redirect_uri` with a code and
the application's `state`.

```mermaid
flowchart LR
  A["/authorize"] --> L["/login"]
  L -->|submit| B{Server answers}
  B -->|ok| N[Next step or back to the application]
  B -->|wrong email or password| L
  B -->|5 failures| T[Throttle or lock screen]
  B -->|429| R[Too many requests]
  T -->|Back to sign in / Try again| L
  R -->|Try again| L
```

## The screen

<StoryScreenshot id="screens-sign-in--default" alt="The sign-in screen: email, password, remember me, forgot password and create account" />

- **Create one** opens `/register` while `auth.registration.enabled` is on, and is absent
  while it is off, the default ([Sign up](https://www.nauthera.io/docs/v1/sign-in-flows/sign-up#sign-up-is-closed)). **Forgot password?** opens `/forgot-password`. Both keep the
  application's request, and both screens link back.
- `login_hint` in the authorization request fills in the email address.
- A session lasts as long as `auth.session.idle_ttl` and `auth.session.absolute_ttl` allow,
  with **Remember me** ticked or not ([Sessions](https://www.nauthera.io/docs/v1/sessions#sessions)).
- **Sign in with a passkey** appears when passwordless sign-in is open and the browser can
  use passkeys ([Passkeys](https://www.nauthera.io/docs/v1/sign-in-flows/passkeys)).
- When the client offers an [LDAP directory](https://www.nauthera.io/docs/v1/ldap-directory-resource#how-sign-in-works),
  the first field takes a username as well as an address (*Username or email*), and
  **Forgot password?** is left out: the directory holds those passwords.
- Identity-provider buttons appear above the form for each provider the client offers
  ([Social sign-in](https://www.nauthera.io/docs/v1/sign-in-flows/social-sign-in)).

<StoryScreenshot id="screens-sign-in--with-social-providers" alt="Sign-in with identity-provider buttons above the form" />
<StoryScreenshot id="screens-sign-in--with-passkey" alt="Sign-in with the passkey button under the form" />

## While it signs in

The **Sign in** button spins and every field and button is disabled until the server
answers, so a double click sends one request.

<StoryScreenshot id="screens-sign-in--submitting" alt="Sign-in submitting: the button spins and the form is locked" />

## When it does not work

**Before anything is sent.** An empty field shows *This field is required*, and an address
that is not one shows *Enter a valid email address*. Nothing reaches the server until both
are valid.

**Wrong email or password.** An unknown address gets exactly the same answer as a wrong
password, *The email or password you entered is incorrect.*, so the screen never reveals
whether an account exists. The form stays filled in.

<StoryScreenshot id="screens-sign-in--with-error" alt="Sign-in failed: the email or password you entered is incorrect" />

**Invited, and no password yet.** An account an administrator invited has no password
until its owner chooses one, so every password is wrong for it. After answering, the
server emails the account a [new invitation](https://www.nauthera.io/docs/v1/sign-in-flows/emailed-links#coming-back-without-the-link),
whose link returns to this application. The wrong-password banner on the password form
adds *If you were invited and haven't chosen a password yet, check your email: we've sent
you a new invitation.* It is shown for every refused password, so it reveals nothing about
the address. A failed passkey does not show it.

<StoryScreenshot id="screens-sign-in--refused-with-invitation-hint" alt="Sign-in failed, with the hint that an invited user has been sent a new invitation" />
<StoryScreenshot id="screens-sign-in--with-field-errors" alt="A field the server refused, marked on the field and in the banner" />

**The server or the network fails.** A server error, a lost connection or an unexplained
failure is a banner on the form; an unexplained one carries a reference ID for an
administrator. The form keeps what was typed, and submitting again works once the problem
is gone.

**A captcha.** A person signs in without one. Once the address typed, or the caller's IP,
has failed `auth.captcha.sign_in_after` times recently (3 by default; `0` never asks), the
server refuses the sign-in until a captcha is solved, and from then on the page solves one
with every attempt. The default provider, `altcha`, is a built-in proof-of-work that runs
in the browser without the user doing anything and calls no third party. A hosted provider
(`turnstile`, `hcaptcha` or `recaptcha`) shows its widget above the button. Either way a
failed check says so and offers to try again. `auth.captcha.provider: none` turns the
captcha off.

<StoryScreenshot id="screens-sign-in--captcha-solving" alt="Running a quick security check before the sign-in is sent" />
<StoryScreenshot id="screens-sign-in--captcha-failed" alt="The security check could not run, with Try again" />

## Throttles and lockout

After `auth.brute_force.max_failed_attempts` wrong passwords (5 by default) the account
locks. With `auth.brute_force.reveal_lockout` off, the default, a locked account looks like
a throttle, so the screen says nothing about whether the account exists; the right password
does not open it either. With it on, the user lands on `/account-locked`, which offers
**Try again** and **Back to sign in**; it does not say when the lock lifts. See
[Rate Limits & Lockout](https://www.nauthera.io/docs/v1/rate-limits).

<StoryScreenshot id="screens-error-pages-rate-limit--with-retry-after" alt="Too many requests, with the time to wait and Try again" />
<StoryScreenshot id="screens-account-locked--too-many-attempts" alt="Account temporarily locked, with Try again and Back to sign in" />

With `reveal_lockout` on, an account an administrator suspended or blocked gets, after the
right password, a banner on the form: *This account is unavailable. Contact your
administrator.* With it off, the default, it is answered like a wrong password.

## A new password is required

An administrator can require a user to replace their password
(`POST /v1/admin/orgs/{slug}/users/{id}/password-change`). The next password sign-in, once
the password and any second factor are proven, ends on `/change-password` instead of in a
session. Setting the new password works like a [reset](https://www.nauthera.io/docs/v1/sign-in-flows/emailed-links#reset-a-password),
and the user then signs in with it. A step that lapsed says so, with **Sign in again**. A
sign-in through an identity provider is not affected.

<StoryScreenshot id="screens-change-password--default" alt="Choose a new password: your administrator asks you to replace your password" />
<StoryScreenshot id="screens-change-password--done" alt="Password changed, with Sign in" />
<StoryScreenshot id="screens-change-password--invalid-token" alt="This step has expired, with Sign in again" />

## Single sign-on and prompt=login

A browser that already holds a session goes from `/authorize` straight back to the
application; `/login` never shows. `prompt=login` in the authorization request asks for the
password again even then.

## Pages around sign-in

- `/` with an authorization request in the query forwards to `/login` with it.
- `/login` answers `404` unless its URL names a registered client, or continues one of the
  server's own screens such as `/device`: a sign-in that continues nothing has nowhere to
  go. Without a `client_id` it shows *Page not found* with the way back to the app.
- An unknown page shows *Page not found* with **Back to login** to the application's
  sign-in.

<StoryScreenshot id="screens-error-pages-not-found--default" alt="Page not found, with Back to login" />
