Skip to main content
For apps that need a password flow — shared devices, no email access, regulatory requirements — Pylon ships built-in email + password auth. Passwords are hashed with Argon2id (the OWASP-recommended algorithm); failed logins run a dummy hash so timing doesn’t leak whether the email exists.

Register

Response (201):
The user is created and signed in — no separate “verify your email” step. If you need email verification, call /api/auth/email/send-verification after sign-in. The user can keep using the app while their email is unverified; gate sensitive flows on User.emailVerified.

Validation rules

displayName defaults to the email if omitted.

Log in

Response (200):
Wrong credentials always return the same shape:
The error message intentionally doesn’t say which one is wrong — and the server runs the password hash check even when the email doesn’t exist, so timing analysis can’t enumerate accounts.

From the SDKs

Why Argon2id?

Pylon uses argon2 with the Argon2id variant — winner of the Password Hashing Competition and the OWASP recommendation. It’s resistant to both side-channel attacks (Argon2i) and GPU-based brute-force (Argon2d) by design. Default parameters: Each hash is self-describing — the algorithm parameters are stored in the hash string so future Pylon versions can rotate them without breaking existing passwords.

Password reset

Pylon ships a built-in “forgot password” flow — two endpoints that email a single-use reset link and swap the password once the user clicks it: 1. Request a reset link. The user submits their email:
Always returns 200 { "sent": true } — whether or not the email is registered. The endpoint is rate-limited and equalizes response timing across the registered / not-registered paths, so it can’t be used to enumerate accounts. When the email exists, Pylon emails a link of the form <public-url>/reset-password?token=<token>. 2. Complete the reset. Your /reset-password page reads the token from the URL and POSTs it with the new password:
The token is single-use. The new password is length- and HIBP-checked (same as register). On success Pylon updates passwordHash, revokes every existing session for that user, and mints a fresh one:
If you’d rather not surface a password-reset UI at all, you can instead run users through the magic-code flow (which mints a session without a password) and update User.passwordHash yourself from an authenticated action.

Configuring the User entity

Password auth expects a User entity with these fields (auto-created by pylon init):
passwordHash is optional because users who signed up via OAuth or magic code never had one. emailVerified is null until they prove control of the email.

Security notes

  • Never deserialize AuthContext from request body. The Rust side intentionally doesn’t derive Deserialize so a client can’t forge is_admin: true. Identity comes from the session lookup, not the wire.
  • /api/auth/session POST is gated — only dev mode or admin token can mint a session for an arbitrary user_id. Your registration/login endpoints are the only public ways to obtain a token.
  • Sessions expire after 30 days by default — see Sessions to change.
  • /login is rate-limited by default — the built-in per-IP/per-email limiter throttles the credential bucket (login, register, reset, TOTP verify) and returns 429 RATE_LIMITED with a retry_after hint on abuse. Pylon Cloud layers additional per-IP limiting at the edge.

When to use password vs alternatives

Use password when:
  • Email isn’t reliable (offline-first apps, regions with poor SMTP delivery)
  • Compliance requires it
  • Users explicitly prefer it
Otherwise prefer magic codes (no memory load, email is verified by construction) or OAuth (zero credentials your service stores).