Common flaws
- Tokens built from a timestamp, user id or short numeric code.
- Tokens that stay valid after use or for days.
- Different messages for existing and unknown emails, which reveals who has an account.
- Reset links built from the request's Host header, which can be poisoned.
- No rate limit on sending or on guessing codes.
Generate a proper token
import { randomBytes, createHash } from 'node:crypto';
const token = randomBytes(32).toString('base64url');
const stored = createHash('sha256').update(token).digest('hex');
// email the token, store only the hash with a 30 minute expiryBehave the same either way
Show 'If an account exists, we sent an email' for every request. Take similar time either way so timing does not leak the answer.
After a reset
- Invalidate the token and all existing sessions.
- Notify the account's email that the password changed.
- Build links from a configured site URL, not the request headers.
Frequently asked questions
How long should a reset token live?
Short. Thirty minutes to an hour is common, and it should work only once.