This version is in beta. Some features may change before release.

Password reset

The password-forgot and password-reset endpoints, the tokenized reset link, its 1-hour TTL, and how a successful reset revokes all active sessions and bearer tokens.

The password-reset flow is mounted automatically by with_default_routes(). It is two steps: the user requests a reset link by email, then submits the new password using the token embedded in the link.

Quick example

No extra builder call is needed - the two endpoints below come with with_default_routes(). Wire a mailer so the token is delivered:

Code
rust
use umbral_auth::{AuthPlugin, AuthUser};
 
AuthPlugin::new()
.with_default_routes() // mounts /api/auth/password-forgot and /api/auth/password-reset
.mailer(/* see Auth mailer */);

Info

Your mailer receives this email as

MailKind::PasswordReset { reset_url }
  • match on it to send a branded reset email with the full tokenized link, or override
templates/auth/email/reset_link.{html,txt}

to change just the wording. See

Auth mailer

.

Endpoints

Both are mounted under the configured prefix (default /api/auth).

POST /api/auth/password-forgot

Code
json
{ "email": "alice@example.com" }

Always returns 202 Accepted with { "detail": "If the address matches a known account, a password-reset link has been sent." } - regardless of whether the email belongs to a registered account. The response is identical in both cases to prevent account enumeration (timing may differ slightly). When the account exists, a single-use token is generated and the reset link is emailed via the configured mailer.

POST /api/auth/password-reset

Code
json
{ "token": "<opaque token from the link>", "new_password": "V10let-Sunset-quay!" }

Verifies the token, applies the new password (through the same argon2 pipeline as registration), and on success:

  1. Marks the token as used (single-use - replaying the same token returns 400).
  2. Revokes all active sessions for the user (post-commit, best-effort).
  3. Revokes all bearer tokens for the user (post-commit, best-effort).
  4. Returns 204 No Content.

The password hash update and the challenge consume are written in a single transaction. Session and bearer-token revocation happens immediately after the transaction commits as a best-effort sweep - a revocation failure is logged but does not undo the password change. In practice, the window between commit and revocation is negligible.

Token rules

1-hour TTL

Tokens expire 60 minutes after issue. Request a new link if it lapses.

Single-use

The token is marked used on first successful reset. Replay returns 400.

Hashed at rest

Only the SHA-256 hash of the token is stored. A DB leak doesn't expose usable tokens.

No enumeration

password-forgot always 202s. Wrong token on password-reset is a generic 400 - same shape as expired.

Password strength

The new password goes through the same validate_password policy as registration. A weak password returns 400 with all failure reasons before any token or session work runs.

The emailed link points at your reset page, not at the API. It is built from the request's (allowed-hosts-validated) Host header as {scheme}://{host}/auth/reset?token=<token> - so you serve a page at /auth/reset that reads the token query parameter and renders a "set a new password" form. That form submits the token plus the new password to the reset endpoint: POST {api_base}/auth/password-reset (JSON) or POST /auth/password-reset (form-action, which redirects). The framework does not ship the reset page itself - it is yours to design.

See the design note docs/decisions/2026-06-28-auth-full-surface.md for the token storage scheme, why sessions and bearer tokens are revoked together, and the enumeration-safe response design.

authpasswordreset