# Registration, verification, and password reset

Enable or disable slices of the auth HTTP API with flags on `LitestarAuthConfig`.

The built-in lifecycle controllers do not use one generic credential field. `identifier` belongs to login
(`LoginCredentials`) only. Registration, email verification, password reset, and the built-in TOTP flow keep their
current email/token-oriented request contracts unless you replace the relevant controller. The public API reference for
those built-in request and response structs are documented on the [Payloads and schemas API](../api/schemas.md) page.

## Registration

With `include_register=True` (default), clients can call `POST {auth_path}/register`.

- **Built-in request body** — `UserCreate` publishes `email` and `password` in OpenAPI.
- **Login identifier** — `login_identifier` is `"email"` or `"username"` and selects how `POST .../login` resolves `LoginCredentials.identifier`. It does not rename the built-in registration fields.
- **Safe creation** — registration uses `BaseUserManager.create(..., safe=True)` so only expected fields (e.g. email + password) are accepted; privileged fields such as `roles`, `is_active`, and `is_verified` are stripped from public registration payloads unless you explicitly opt into privileged manager calls. The built-in `UserCreate` request schema is strict, and custom `user_create_schema` values passed to `create_register_controller(...)` must also set `forbid_unknown_fields=True`, so stale request keys fail with `REQUEST_BODY_INVALID` instead of being silently ignored. Direct `create(..., safe=False)` calls must declare any app-owned fields in the manager's `creatable_fields` allowlist. Grant superuser access by assigning the configured superuser role instead.
- **Built-in response body** — successful register/verify/reset responses use `UserRead`, which includes normalized `roles` alongside the existing account-state fields. New users start with `roles=[]` unless a privileged path assigns them.
- **Failure surface** — registration is enumeration-resistant. Duplicate identifiers, password-policy failures,
  and manager authorization rejections all return the same 400 / `REGISTER_FAILED` response with the generic
  detail `Registration could not be completed.` Specific Python exceptions remain available inside the
  manager/controller boundary for operator diagnostics. When a duplicate identifier is detected, the manager also
  invokes `on_after_register_duplicate(user)` with the existing account so you can enqueue an out-of-band owner
  notification without changing the public response.
- **Timing envelope** — plugin-owned registration responses are also padded to
  `LitestarAuthConfig.register_minimum_response_seconds` (default `0.4`) on both success and domain-failure
  paths. The minimum-duration wait is a lower-tail timing-oracle defense and does not replace rate limiting. Keep
  duplicate-register notification work in a background queue; slow synchronous I/O in `on_after_register_duplicate`
  can still make duplicate attempts observably slower.
- **Persistence boundary** — relational `role` / `user_role` tables are an internal storage detail of the ORM layer. Registration still accepts flat user fields only, and this route surface does not expose role-catalog management or RBAC policy payloads. Use the opt-in [HTTP role administration](role_admin_http.md) guide or the operator CLI when you need admin workflows.

## Email verification

With `include_verify=True`:

- `POST .../request-verify-token` — `RequestVerifyToken` with `email`; issues a new verification
  token when the email belongs to an existing unverified user, while keeping the public response and
  manager hook contract enumeration-resistant.
- `POST .../verify` — `VerifyToken` with `token`; consumes a verification token.

`LitestarAuthConfig.requires_verification` defaults to `True`, so newly registered accounts must
verify their email before `/login` or built-in `/2fa/verify` can complete unless you set
`requires_verification=False`.

The library **does not send email**. Implement `on_after_request_verify_token` and related hooks on
your user manager to enqueue mail or notifications, and make sure the hook performs equivalent async
work even when it receives `user=None` / `token=None` for unknown or already-verified emails.

## Password reset

With `include_reset_password=True`:

- `POST .../forgot-password` — `ForgotPassword` with `email`; returns **202 Accepted** with the same shape whether the email exists (enumeration-safe). When rate limits apply, the counter increments after handler completion without exposing whether the user existed.
- `POST .../reset-password` — `ResetPassword` with `token` + `password`.

Reset tokens are tied to a password fingerprint so they invalidate after a successful password change. Implement `on_after_forgot_password` to send the link out-of-band.

## TOTP boundary

`login_identifier="username"` does not make the built-in 2FA flow username-based. TOTP enrollment and default password step-up still use `user.email`, and the enrollment response still returns an email-based otpauth URI.

## User schema helpers

The password-wiring contract is documented in
[Configuration](../configuration/manager.md#manager-password-surface). For custom registration
DTOs, reuse `litestar_auth.schemas.UserEmailField` and `litestar_auth.schemas.UserPasswordField`
when you want the built-in email/password metadata without copying local constraints. Those aliases only affect schema validation and OpenAPI. Runtime password
policy still comes from `password_validator_factory` or the manager's default
`require_password_length` validator.

When you keep the built-in register/verify/reset/users controllers but replace `user_read_schema`
or `user_update_schema`, keep the default role-aware contract in mind: built-in `UserRead`
includes `roles`, built-in `AdminUserUpdate` accepts optional `roles`, and the self-service
`UserUpdate` is limited to email plus `current_password` proof for email changes — privileged
fields (`is_active`, `is_verified`, `roles`) are rejected at msgspec decode on `/users/me` so the
persistence layer never sees a self-service body that tries to escalate. Admin `PATCH /users/{id}`
continues to persist those fields through `AdminUserUpdate`. Outside the built-in controllers, direct
`BaseUserManager.update(...)` calls must pass `allow_privileged=True` before mutating
`is_active`, `is_verified`, or `roles`.

## Related

- [Backends](../configuration/backends.md#paths-and-http-feature-flags) — `include_register`, `include_verify`, `include_reset_password`, `login_identifier`.
- [Extending](extending.md) — hooks on `BaseUserManager`.
- [Payloads and schemas API](../api/schemas.md) — built-in auth lifecycle DTOs from `litestar_auth.payloads` plus the default user CRUD schemas from `litestar_auth.schemas`.
