# Password sign-in (hosted auth UI)

`POST /v1/auth/sign-in`

Verifies a user's email + password with layered brute-force defense and,
on success, establishes an interactive login session (Set-Cookie) and
returns `redirectTo`: the /authorize continuation the browser follows so a
single server path issues the authorization code (and later clients reuse
the session). The body ALSO carries the preserved OAuth authorization
parameters, which the server RE-VALIDATES against the client registry —
the SPA-supplied `redirect_uri`/`scope` are never trusted. Failures return
a uniform problem+json (401 invalid credentials, 429 throttled/locked) so
the endpoint cannot be used to enumerate accounts.

An account an administrator suspended or blocked is refused only after
its password verifies. By default the refusal is the wrong password's
401, identical in status and body. A deployment that reveals lockouts
(`auth.brute_force.reveal_lockout`) answers 403 `account_unavailable`
instead, and only to the caller who presented the right password.

An account an administrator requires to set a new password gets no
session: once its password, and any second factor, are proven, the
answer's `redirectTo` is the page that sets one, carrying a single-use
link in its fragment and the authorization request in its query. A
sign-in through an identity provider is unaffected.

With a captcha configured, a sign-in for an address, or from a caller,
that failed `auth.captcha.sign_in_after` times recently must carry a
`captcha` solution, and is refused with 400 `captcha_failed` until it
does.

A sign-in must continue something: a `client_id` registered in the
organization, or a `continue_path` naming the device verification page.
One with neither is refused with 404 `not_found` before the password is
checked, and the hosted sign-in screen answers 404 for it as well.

## Authorization

None: this endpoint does not take API credentials.

This endpoint reads the browser session from the `Cookie` header: The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.

This endpoint is rate limited; it answers `429` when a limit is hit.

## Headers

- `Origin` (string, optional): Browser Origin, checked (with Sec-Fetch-Site) as a same-origin/CSRF guard.
- `Sec-Fetch-Site` (string, optional): Fetch metadata; only same-origin is trusted (CSRF guard).
- `Content-Type` (string, optional): Must be application/json — a JSON body cannot be a CORS simple request, closing form-based CSRF.
- `User-Agent` (string, optional)
- `Accept-Language` (string, optional): The browser's preferred languages (RFC 9110 §12.5.4); a message the operation queues, such as a new-sign-in alert, is written in the first one the server ships.
- `Cookie` (string, optional): The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.

## Request body (`application/json`)

A hosted-UI password sign-in. `email` + `password` are the credentials; the remaining fields are the preserved OAuth authorization parameters the server re-validates against the client registry (never trusted as-is). The field set is exhaustively enumerated so it stays in lockstep with the SPA's authQuery.PRESERVED_PARAMS.

- `email` (string, required): The login identifier (email). Normalized (lowercased) server-side. Constraints: 1–320 characters. Example: `"alice@example.com"`.
- `password` (string, required): Constraints: 1–4096 characters.
- `remember` (boolean, optional): Request a longer-lived session (advisory).
- `captcha` (string, optional): The captcha solution, required once the address typed or the caller's address failed `auth.captcha.sign_in_after` times recently: the sign-in is refused with `captcha_failed` until it carries one. Constraints: at most 4096 characters.
- `client_id` (string, optional): Constraints: at most 256 characters.
- `ui_locales` (string, optional): Constraints: at most 256 characters.
- `redirect_uri` (string, optional): Constraints: at most 2048 characters.
- `response_type` (string, optional): Constraints: at most 32 characters.
- `scope` (string, optional): Constraints: at most 2048 characters.
- `state` (string, optional): Constraints: at most 1024 characters.
- `nonce` (string, optional): Constraints: at most 1024 characters.
- `code_challenge` (string, optional): Constraints: at most 128 characters.
- `code_challenge_method` (string, optional): Constraints: at most 16 characters.
- `login_hint` (string, optional): Constraints: at most 512 characters.
- `prompt` (string, optional): Constraints: at most 64 characters.
- `request_uri` (string, optional): Constraints: at most 512 characters.
- `resource` (string, optional): Constraints: at most 2048 characters.
- `authorization_details` (string, optional): Constraints: at most 8192 characters.
- `dpop_jkt` (string, optional): Constraints: exactly 43 characters; pattern `^[A-Za-z0-9_-]+$`.
- `max_age` (integer (int64), optional): Constraints: at least 0.
- `continue_path` (string, optional): Constraints: at most 128 characters.

No other properties are accepted.

## Responses

### 200 OK

Authentication succeeded (a session was established) or a next step is required.

Content type: `application/json`

Headers:
- `Set-Cookie` (array of strings)
- `Cache-Control` = `no-store`

The next step for the hosted UI after an auth action. `redirectTo` (when present) is a server-built URL the browser follows — the /authorize continuation or the final client redirect. The MFA/consent fields are reserved for the interactive flows layered on top of this step.

- `redirectTo` (string, optional): URL to navigate to next (the /authorize continuation or client redirect). Example: `"/authorize?client_id=nauthera_2Rk9&response_type=code&..."`.
- `mfaRequired` (boolean, optional): An MFA challenge is required before completing sign-in.
- `mfaMethods` (array of strings, optional): Available MFA methods, most-preferred first: on a challenge the one the user answered with last leads, and the user may choose any other.
- `consentRequired` (boolean, optional): User consent is required before issuing tokens.
- `setupRequired` (boolean, optional): The user has no active second factor the policy permits and must enroll one of `mfaMethods` before sign-in can complete.
- `emailVerificationRequired` (boolean, optional): The organization requires a verified address and this account's is not. No session was established; a verification link was sent if one could be, and POST /v1/auth/verify-email/resend sends another.
- `passkeyOffered` (boolean, optional): The password verified and the account holds no passkey where passwordless sign-in is open: no session yet, and the user is offered to add one through the passkey registration ceremony, or declines through POST /v1/auth/mfa/passkey/offer/decline, either of which completes the sign-in.

### 429 Too Many Requests

Too many requests.

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### Error

Problem details (RFC 9457).

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

## Example request

```bash
curl -X POST 'https://auth.example.com/v1/auth/sign-in' \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "correct-horse-battery-staple"
  }'
```

## Example response (200)

```json
{
  "redirectTo": "/authorize?client_id=nauthera_2Rk9&response_type=code&..."
}
```
