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:
use umbral_auth::{AuthPlugin, AuthUser}; AuthPlugin::new() .with_default_routes() // mounts /api/auth/password-forgot and /api/auth/password-reset .mailer(/* see Auth mailer */);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
{ "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
{ "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:
- Marks the token as used (single-use - replaying the same token returns
400). - Revokes all active sessions for the user (post-commit, best-effort).
- Revokes all bearer tokens for the user (post-commit, best-effort).
- 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.
What a reset link looks like
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.