Account Recovery Workflows: Core Concepts
Account Recovery Workflows: Core Concepts
Users forget passwords. They lose their phones. They drop their hardware keys in the ocean. Account recovery is the mechanism that lets users regain access to their accounts when they cannot authenticate normally. It is also one of the most attacked components of any identity system — an attacker who can bypass account recovery does not need the password.
The Security-Usability Tension
Account recovery sits at the intersection of security and usability. Make it too strict, and legitimate users cannot recover their accounts, flooding the help desk. Make it too lenient, and attackers can social engineer their way into any account. The goal is to make recovery as difficult for an attacker as it is for a legitimate user.
Common Recovery Methods
Email-based recovery — The system sends a recovery link to the user's registered email address. This is the most common method, but it is only as secure as the email account itself. If the attacker has access to the user's email, they can reset the password for every service that uses email recovery.
SMS-based recovery — A code is sent to the user's phone number. Vulnerable to SIM swapping and SS7 interception. Should not be the sole recovery method.
Security questions — The user answers pre-registered questions ("What was your first pet's name?"). Weak because answers are often publicly discoverable through social media or data breaches.
Backup codes — A set of single-use codes generated at enrollment time and stored by the user (printed or saved in a password manager). Strong because they are offline and unpredictable, but users often lose them.
Trusted contacts — The user designates friends or family who can vouch for their identity. Used by Facebook and some other services. Difficult to implement well.
In-person verification — The user visits a physical location with identity documents. Strong but impractical for most online services.
Token Security Requirements
Recovery tokens (links or codes) must satisfy three properties:
- Unpredictable — Generated using a cryptographically secure random number generator (CSPRNG). An attacker must not be able to guess or enumerate valid tokens.
- Single-use — Once a token is used, it is invalidated. This prevents replay attacks.
- Time-limited — Tokens expire after a short window (typically 15-60 minutes). This limits the window of opportunity for an attacker.
import secrets
import hashlib
import time
def generate_recovery_token():
"""Generate a cryptographically secure recovery token."""
# 32 bytes of randomness = 256 bits of entropy
raw_token = secrets.token_urlsafe(32)
# Store only the hash, not the plaintext token
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
return raw_token, token_hash
def verify_recovery_token(raw_token, stored_hash, expires_at):
"""Verify a recovery token."""
if time.time() > expires_at:
return False # Token expired
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
return secrets.compare_digest(token_hash, stored_hash)
Storing only the hash means that even if the database is compromised, the attacker cannot use the recovery tokens.
Where It Fits
Account recovery is the safety net for authentication. It must be as carefully designed as the primary authentication flow, because attackers often target recovery as the weakest link. Study SSO and Federation first to understand how recovery works across multiple systems, then move to the OWASP Top 10 to see how recovery vulnerabilities manifest in real-world attacks.
Account Recovery Workflows: Practice
Account Recovery Workflows: Practice
Account recovery is where theoretical security meets real users. The exercises below cover the most common implementations and their pitfalls.
Exercise 1: Implement Secure Email Recovery
import secrets
import hashlib
import time
from datetime import datetime, timedelta
class AccountRecovery:
def __init__(self, db, email_service):
self.db = db
self.email_service = email_service
def initiate_recovery(self, email):
user = self.db.find_user_by_email(email)
if not user:
# Do not reveal whether the email exists
return # Silently return to prevent enumeration
# Generate a secure token
raw_token = secrets.token_urlsafe(32)
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
# Store the hash with a 15-minute expiration
self.db.store_recovery_token(
user_id=user.id,
token_hash=token_hash,
expires_at=datetime.now() + timedelta(minutes=15),
used=False
)
# Send the raw token to the user's email
recovery_link = f"https://app.example.com/reset-password?token={raw_token}"
self.email_service.send(email, "Password Reset", f"Click: {recovery_link}")
def reset_password(self, raw_token, new_password):
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
recovery_record = self.db.find_recovery_token(token_hash)
if not recovery_record:
return False # Invalid token
if recovery_record.used:
return False # Token already used
if datetime.now() > recovery_record.expires_at:
return False # Token expired
# Update the password
self.db.update_password(recovery_record.user_id, new_password)
# Invalidate the token
self.db.mark_token_used(recovery_record.id)
# Invalidate all existing sessions for this user
self.db.invalidate_all_sessions(recovery_record.user_id)
return True
Key security properties:
- The raw token is sent to the user's email; only the hash is stored in the database
- The token expires after 15 minutes
- The token is single-use
- All existing sessions are invalidated after a password reset
Exercise 2: Prevent Account Enumeration
An attacker can use the recovery flow to discover which email addresses are registered:
# VULNERABLE: Different responses for existing vs non-existing emails
def initiate_recovery_vulnerable(email):
user = db.find_user_by_email(email)
if user:
send_recovery_email(user)
return "Recovery email sent."
else:
return "Email not found." # Attacker learns the email is not registered
# SECURE: Same response regardless of whether the email exists
def initiate_recovery_secure(email):
user = db.find_user_by_email(email)
if user:
send_recovery_email(user)
# Always return the same message
return "If this email is registered, a recovery link has been sent."
Exercise 3: Implement Backup Codes
import secrets
import hashlib
def generate_backup_codes(count=10):
"""Generate a set of single-use backup codes."""
codes = []
for _ in range(count):
# 8-character alphanumeric code
code = secrets.token_urlsafe(6)[:8].upper()
codes.append(code)
return codes
def store_backup_codes(user_id, codes):
"""Store hashed backup codes."""
for code in codes:
code_hash = hashlib.sha256(code.encode()).hexdigest()
db.store_backup_code(user_id, code_hash, used=False)
def verify_backup_code(user_id, code):
"""Verify and consume a backup code."""
code_hash = hashlib.sha256(code.encode()).hexdigest()
backup_code = db.find_backup_code(user_id, code_hash)
if not backup_code or backup_code.used:
return False
db.mark_backup_code_used(backup_code.id)
return True
Check Your Understanding
- Why should you store only the hash of a recovery token, not the plaintext?
- What is account enumeration and how does it relate to recovery flows?
- Why should all sessions be invalidated after a password reset?
Account Recovery Workflows: Trade-offs and Interviews
Account Recovery Workflows: Trade-offs and Interviews
Account recovery is one of the most difficult security problems to get right. It must be usable enough for legitimate users while being resistant to determined attackers.
When Account Recovery Is the Wrong Tool
n- High-value accounts: For accounts with access to sensitive data (financial systems, admin panels), self-service recovery may be too risky. Require in-person verification or a video call with identity verification.
- Shared accounts: Recovery does not work for shared accounts because there is no single user to verify. Use a password manager with shared vaults instead.
- Automated systems: Service accounts and API keys should not have recovery flows. Use a secrets manager with automatic rotation instead.
Common Attack Vectors
- Email account compromise: If an attacker controls the user's email, they can reset passwords for all connected services. Mitigate by using multiple recovery channels and monitoring for suspicious recovery attempts.
- SIM swapping: An attacker ports the victim's phone number to a new SIM to intercept SMS recovery codes. Mitigate by avoiding SMS for recovery of high-value accounts.
- Social engineering: An attacker calls the help desk and convinces them to reset the password. Mitigate by training help desk staff and requiring identity verification through multiple channels.
- Token prediction: If recovery tokens are generated with a weak PRNG, an attacker can predict them. Mitigate by using a CSPRNG (secrets module in Python, crypto.randomBytes in Node.js).
- Token replay: If tokens are not invalidated after use, an attacker can reuse a stolen token. Mitigate by making tokens single-use.
Designing a Defense-in-Depth Recovery Strategy
No single recovery method is sufficient. A robust strategy layers multiple methods:
- Primary: Email recovery with a secure, single-use, time-limited token
- Secondary: Backup codes stored offline by the user
- Tertiary: Trusted contacts or in-person verification for high-value accounts
- Monitoring: Alert on recovery attempts from unusual locations or devices
- Rate limiting: Limit the number of recovery attempts per account and per IP
How This Comes Up in Interviews
- "How do you secure account recovery?" Use cryptographically secure random tokens, store only hashes, make tokens single-use and time-limited, prevent account enumeration, invalidate all sessions after reset, and monitor for suspicious recovery attempts.
- "What is the weakest link in most account recovery flows?" Email recovery. If the user's email is compromised, every service that uses email recovery is vulnerable. The second weakest link is security questions, whose answers are often publicly discoverable.
- "How do you handle account recovery for high-value accounts?" Require multiple verification factors, use in-person or video verification, implement a waiting period before the reset takes effect, and notify the user through multiple channels.
Practice Problems
Solution
1) Generation: Use a CSPRNG (e.g., secrets.token_urlsafe(32)) to generate 256 bits of randomness. 2) Storage: Store only the SHA-256 hash of the token, never the plaintext. 3) Validation: Check that the token exists, has not been used, and has not expired (15-60 minute window). 4) After use: Invalidate the token and all existing sessions for the user. Solution
Return the same response regardless of whether the email exists: 'If this email is registered, a recovery link has been sent.' Do not reveal whether an email is registered. Additionally, rate-limit recovery attempts per IP and per email to prevent enumeration at scale. Quiz
1. What are the three security properties of a recovery token?
2. Why should you store only the hash of a recovery token, not the plaintext?
3. What is account enumeration in the context of password recovery?
4. Why should all existing sessions be invalidated after a password reset?
Flashcards
Question
What are the three security properties of a recovery token?
Click to reveal answer
Answer
Unpredictable (CSPRNG), single-use (invalidated after use), and time-limited (expires after 15-60 minutes).
Question
Why store only the hash of a recovery token?
Click to reveal answer
Answer
If the database is compromised, the attacker cannot reverse the hash to recover the plaintext token.
Question
What is account enumeration?
Click to reveal answer
Answer
Using the recovery flow to discover which email addresses are registered, by observing different responses for existing vs non-existing emails.
Question
Why invalidate all sessions after a password reset?
Click to reveal answer
Answer
To ensure an attacker with a stolen session token cannot continue using the account after the password is changed.
Question
What is the weakest link in most account recovery flows?
Click to reveal answer
Answer
Email recovery. If the user's email is compromised, every service that uses email recovery is vulnerable.
Revision Notes
Key Takeaways
- 1. Recovery links must be single-use, time-limited, and unpredictable in tokens.
- 2. Recovery workflows are a common attack surface and must balance security with usability.
- 3. Store only the hash of recovery tokens, never the plaintext.
- 4. Prevent account enumeration by returning the same response for all emails.
- 5. Invalidate all sessions after a password reset to prevent session hijacking.
Interview Tips
- • Explain the three security properties of recovery tokens and why each matters.
- • Describe common attack vectors (email compromise, SIM swapping, social engineering) and their mitigations.
- • Discuss how to prevent account enumeration in recovery flows.
- • Connect account recovery to the broader identity security landscape: authentication, MFA, and session management.
Cheat Sheet
Account Recovery Workflows Cheat Sheet
Phase: Identity, Authentication, and Authorization | Level: intermediate
Token Security:
- Unpredictable: CSPRNG (secrets.token_urlsafe(32))
- Single-use: invalidate after use
- Time-limited: 15-60 minute expiration
- Store only the hash, never plaintext
Common Methods:
- Email recovery: most common, only as secure as the email account
- SMS recovery: vulnerable to SIM swapping
- Security questions: weak, answers often public
- Backup codes: strong but users lose them
- In-person verification: strongest but impractical
Key Protections:
- Prevent account enumeration (same response for all emails)
- Rate-limit recovery attempts
- Invalidate all sessions after reset
- Monitor for suspicious recovery patterns
Tags: identity, account-security
Continue Learning
Before You Start
- No prerequisites listed.