Password authentication
Design safe signup
Normalize account identifiers, validate input, allow password managers, and create accounts without leaking whether another user exists.
8 minute lesson
Signup creates security-critical records from untrusted input. Decide which identifier is canonical and which fields require later verification.
Normalize email according to a documented conservative policy, enforce database uniqueness, allow long passphrases and password-manager paste, and avoid arbitrary composition rules. Never silently truncate a password. Return a generic outcome where enumeration risk matters.
Let the database settle concurrent requests:
CREATE UNIQUE INDEX users_email_unique
ON users (normalized_email);
An application-level “does this email exist?” check can race. Two requests may both see no row and then insert. The unique constraint is the final invariant; the handler translates its error into the product’s chosen response.
Keep the original email for display and delivery if needed, and store a normalized form for lookup. Avoid aggressive provider-specific rewriting, such as removing dots or tags, unless your product owns that policy and its consequences.
Create the account in an unverified state. Sending an email proves only that a message was attempted. The account becomes verified after a valid, scoped, unexpired token is consumed.
Password rules should work with password managers. Allow paste, spaces, Unicode, and at least 64 characters. As a current baseline, treat passwords shorter than 15 characters as weak when MFA is absent and shorter than 8 when MFA is required. Check known-compromised choices without sending the full password to another service.
Define the signup request schema, unique constraint, duplicate behavior, and verification state for the Books API. Race two requests for the same email and inspect the final rows.
Lesson completed