In this lesson, you will learn how to design password reset tokens that are cryptographically secure and resistant to reuse attacks.
When a user requests a password reset, your Node.js application must generate a secret string—a reset token—and send it to them via email. If this token is predictable, an attacker could guess it to reset someone else's password. If it is reusable, an attacker who intercepts the link could change the password repeatedly.
Designing Secure Tokens
To prevent these vulnerabilities, your tokens must be both unpredictable and short-lived.
- High Entropy: Never use simple strings like user IDs or timestamps. Use a cryptographically secure random generator, such as the
cryptomodule in Node.js, to generate a buffer of at least 32 bytes, then encode it (e.g.,base64url). - Hashing at Rest: Just like passwords, you should store the hash of the token in your database, not the raw token. When the user clicks the link, you hash the provided token and compare it to the stored hash. This ensures that even if your database is breached, the attacker cannot immediately generate valid reset links.
- Single-Use Logic: Once a user successfully updates their password using a token, that token must be invalidated immediately by either deleting it or marking it as "used" in the database.
Implementation Pattern
Here is a simplified approach to generating and verifying a secure token:
const crypto = require('crypto');
// Generate a secure token
const rawToken = crypto.randomBytes(32).toString('base64url');
const hashedToken = crypto.createHash('sha256').update(rawToken).digest('hex');
// Store hashedToken in DB with an expiry timestamp
// ... save to DB ...
// When user submits reset:
const providedHash = crypto.createHash('sha256').update(providedRawToken).digest('hex');
// Query DB: WHERE token_hash = ? AND expires_at > NOW()
By enforcing a strict expiration (e.g., 15 minutes) and checking the hash against your database, you ensure that the link has a narrow window of utility and cannot be replayed once the action is complete. Always bind the token to the specific user's identity in your database to ensure it cannot be used for any other account.
Key Takeaway: Generate high-entropy, short-lived tokens, store only their hashes in your database, and mark them as invalidated immediately upon successful password update.