Username & password
Email and password is the default credential. When someone signs up on your issuer's login page, auth.is creates a user in that issuer's pool with a password credential — hashed with argon2id, never stored in the clear, never reversible. Everything on this page (signup, verification, reset) is served by the issuer itself; you never build these screens.
Passwords are argon2id, not encrypted. A password is put through a one-way argon2id hash and only the digest is stored — auth.is cannot recover or reveal it, only verify a supplied password against it. That is different from a confidential client's secret, which is reversibly encrypted (AES-GCM) so the token endpoint can check it. Do not conflate the two: user passwords are hashed, client secrets are encrypted. See Clients for the secret story.
Turning signup on
By default a client's login page only lets existing users sign in — there is no "Create account" link. Self-signup requires two flags, both on:
- The client's
registration_enabledset totrue(viacreate_client/update_client). - The issuer's
registrationModeset toopen, notinvite_only(viaupdate_issuer_settings).
Setting registration_enabled=true on the client is not enough on its own. If the issuer's registrationMode is invite_only, self-signup stays off everywhere, client flag or not. You need both. This is enforced server-side on every signup request — the "Create account" link is a convenience, never the authority, so hiding the UI is not what gates registration.
The signup flow
- The user submits an email and a password. Passwords must be at least 8 characters — the only complexity rule (length is the honest signal; the rest is theatre).
- auth.is creates an unverified user and its argon2id password credential, then emails a short verification code to the address.
- The verify screen takes the code. A correct code marks the email verified and the account is usable.
Email verification matters beyond hygiene: the account-linking rule only auto-links a federated login to a password account when both the local email and the upstream email are verified. An unverified account never gets silently claimed.
Enumeration parity — a feature, not an accident
auth.is deliberately refuses to tell an anonymous visitor whether an email already has an account. Every user-facing surface is built to leak nothing:
- Login returns a single generic failure. "Wrong password", "no such user", "no password set", and "rate limited" all produce the identical result with no reason code. A miss even burns the same argon2 work as a hit (verifying against a dummy hash), so there is no timing oracle either.
- Signup for an existing address does not error. It performs the same argon2 work, shows the same verify screen, and emails a "you already have an account" notice instead of a code — so the flow is indistinguishable from a fresh signup, but can never be completed (no usable code is issued).
- Password reset for an unknown address behaves the same way: same screen, a "no account here" notice instead of a code, no completable path.
Because the responses are intentionally identical, your integration cannot use these endpoints to probe whether an email is registered — that is the point. If a user insists they have no account but got a "you already have an account" email, the honest answer is that an account exists for that address; direct them to password reset.
Password endpoints are also rate-limited on two dimensions at once — per (issuer, email) and per IP — so credential stuffing is throttled before it reaches argon2. Login allows 5 attempts per email per 15 minutes (and 30 per IP); signup, verify, and reset have their own buckets, and a code resend has a 60-second cooldown.
Password reset
The "forgot password" flow mirrors signup: request a reset for your email, receive a code (or the silent decoy for an unknown address), and set a new password of at least 8 characters.
A completed reset sweeps the account clean. After the new password is set, auth.is revokes every session and grant the account holds within the issuer — so a stolen session cookie stops auto-authorizing and any offline_access refresh token stops working immediately, not at expiry. A password reset is treated as credential recovery: the user lands in a single, clean session signed in with the new password. See how grants are swept in Sessions & SSO and the revocation mechanics in the curl guide.
What a password login yields
A password login produces exactly the same tokens as any other login — the credential type is invisible to your application. You get an ID token carrying the claims the granted scopes allow (sub, and email / email_verified / profile fields), with email_verified reflecting whether the account completed verification. See Scopes & claims for the full map.
Next steps
Users & identities
the pool a signup lands in, and how credentials attach to one user.
Passkeys
offer a passkey as a second factor on top of the password.
Identity providers
let users skip the password with "Continue with …".
Clients
the `registration_enabled` flag and the issuer's `registrationMode`.
No framework (curl)
watch a login return real tokens.