ACC-69Required
Make sign-in and reset links single-use and short-lived
A link that signs someone in or resets a password works once, expires within an hour, and holds a long random token. A sign-in code expires within 15 minutes.
How to do it
Make the token at least 128 random bits from a secure random source. Store only a hash of the token, tied to the account and the request. Mark it used on first success. A typed sign-in code dies after 5 wrong tries, and the person asks for a new one. Open a page with a button instead of acting on the first visit, because mail scanners open links on their own. Send Referrer-Policy: no-referrer on that page so the token never leaks. A link only proves access to the inbox. NIST SP 800-63B forbids email as an authentication factor, so ask for the second factor too where the account has one.
Examples
Plain text example
GET /reset?token=… → shows "Set a new password" form, changes nothing
POST /reset → checks token, sets password, marks token used
JavaScript example
import { randomBytes, createHash } from 'node:crypto';
const token = randomBytes(32).toString('base64url');
const tokenHash = createHash('sha256').update(token).digest('hex');
await db.saveResetToken({ userId, tokenHash, expiresAt: Date.now() + 60 * 60 * 1000 });
Python example
import hashlib, secrets, time
token = secrets.token_urlsafe(32)
token_hash = hashlib.sha256(token.encode()).hexdigest()
db.save_reset_token(user_id=user_id, token_hash=token_hash, expires_at=time.time() + 60 * 60)
How to check
Use a reset link twice. The second use fails. Wait past the expiry with a fresh link, and it fails too. Fetching the link with curl changes nothing.
Sources
Related rules
- Keep personal data out of addresses in Website
- Keep secrets out of addresses in API design
- Retry with backoff and give up in time in Email
- Make password resets safe in Security testing
- Make tokens from a secure random source in Security testing
- Prove the email address before trusting it in Accounts
- Weigh email links before making them the only way in in Accounts
- Make invitations expire and work once in Accounts
- Keep the old email until the new one is confirmed in Accounts
- Format code texts for automatic fill in Notifications
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/accounts/sign-in-methods/sign-in-links">Agent Wranglers accounts playbook</a>. In code you write, add a comment next to it with the same link.