← Blog
Cybersecurity

Replace recoverable passwords with safe account recovery

When a requirement says support staff must be able to retrieve a user’s password, the safest fix is architectural: remove retrieval from the workflow. A password that can be displayed, emailed, or decrypted by the appli…

By Emmanuel Corels

When a requirement says support staff must be able to retrieve a user’s password, the safest fix is architectural: remove retrieval from the workflow. A password that can be displayed, emailed, or decrypted by the application is a reusable credential waiting to be disclosed by a database breach, application compromise, insider, log, backup, or support tool.

Translate “retrieve my password” into the real need

Stakeholders usually need one of four outcomes: help a legitimate user regain access, support a user with accessibility needs, let an administrator enter an account for support, or authenticate to a legacy third-party system. None of the first three requires recovering the user’s secret.

  • Use a single-use, expiring reset or recovery flow for lost access.
  • Use audited support impersonation with narrow roles and explicit user notice where support must reproduce an issue.
  • Offer assisted recovery that verifies identity without asking an agent to know or choose the user’s password.
  • For a legacy downstream credential, isolate it as a separate machine secret and pursue token-based delegation; do not call it the user’s application password.

Document the data flow before changing code: every credential column, encryption key, backup, replica, analytics export, log field, email template, support screen, and API response. A UI change alone does not remove recoverable copies.

Store a verifier, not a reversible secret

Use the framework’s maintained password API with a modern, adaptive password-hashing scheme. OWASP’s current guidance prefers Argon2id where available, with scrypt, bcrypt, or PBKDF2 in defined circumstances. Each record needs a unique salt; mature APIs store algorithm and cost metadata in the encoded hash.

// Illustrative PHP flow; benchmark policy on production-class hardware.
$hash = password_hash($password, PASSWORD_ARGON2ID);

if (!password_verify($candidate, $hash)) {
    deny_login();
}

if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
    replace_hash_after_successful_login($userId, $candidate);
}

Never log $password or $candidate. Clear references when practical, minimize their lifetime, and keep transport protected with TLS. A pepper may add defense in depth when stored outside the password database, but it creates key-management and rotation obligations and does not replace a suitable password hash.

Design recovery as a short-lived authorization ceremony

A reset request should return the same outward message and similar timing whether the account exists or not. Rate-limit by account and network signals without allowing attackers to lock out a known user. Generate tokens with a cryptographically secure random generator, store only a protected representation, bind each token to one purpose and account, and make it single-use with a short expiry.

rawToken = secureRandom(32 bytes)
storedToken = HMAC(serverRecoveryKey, rawToken)

store(userId, storedToken, purpose="password-reset",
      expiresAt, usedAt=null)

Build reset URLs from a fixed trusted base URL, not an untrusted Host header. Serve them only over HTTPS and prevent token leakage through referrers, analytics, logs, and third-party content. After validation, let the user choose a new password, store its verifier, atomically consume the token, notify the account owner, and offer or enforce invalidation of existing sessions. Do not email a new password and do not automatically log the user in merely because a reset completed.

Accessible support does not require weaker secrets

For people who struggle with a standard email link, offer multiple tested recovery channels and a human-assisted path. Agents can guide navigation, issue a time-limited recovery code after approved identity checks, or arrange higher-assurance offline recovery. Use plain language, large targets, screen-reader labels, sufficient time with a safe restart, and confirmation that does not expose whether an account exists to an unauthenticated caller.

Support agents should never ask users to read their current password aloud or set a staff-known password. Record the agent, reason, assurance method, and recovery event—not the secret. High-risk accounts may require a second approver, stronger recovery evidence, a cooling-off period, or notification through an already verified channel.

Migrate recoverable credentials deliberately

  1. Disable every new read and write of plaintext or decryptable passwords.
  2. Deploy modern hashing for new passwords and successful legacy logins.
  3. Force a reset for accounts whose only safe migration path is a new secret.
  4. Delete encrypted/plaintext columns after the application no longer depends on them.
  5. Rotate encryption keys and database credentials that protected the old store.
  6. Expire backups and replicas through documented retention; treat surviving copies as sensitive until gone.
  7. Search logs, exports, tickets, email, and observability systems for leaked copies and handle findings under incident policy.

Do not decrypt every credential into a migration file. If a reversible legacy value must be verified during a staged migration, contain that path, measure its use, and give it an expiration date. Prefer requiring a reset over extending the unsafe design.

Verify security and operations

Test unknown and known accounts for response consistency; expired, reused, modified, and cross-account tokens; concurrent redemption; rate limiting; session invalidation; host-header injection; referrer leakage; support authorization; and notification delivery. Confirm database dumps contain hashes and protected recovery-token representations, never usable passwords or live raw tokens.

Monitor reset request and completion rates, delivery failures, token-reuse attempts, support overrides, and unusual recovery-channel changes. Keep logs free of secrets. Exercise customer-support runbooks with accessibility users and incident responders, including loss of the email account and compromise of an existing session.

Failure modes, rollback, and incident recovery

  • Fast unsalted hash: migrate on login and force resets for dormant accounts; SHA-256 alone is not password storage.
  • Encrypted password column: compromise of the application and key can expose every password at once.
  • Long-lived or reusable reset links: shorten expiry, consume atomically, and revoke outstanding tokens after use.
  • User enumeration: align messages, status codes, and timing; queue delivery asynchronously.
  • Support bypass: least privilege, step-up checks, dual control for sensitive accounts, immutable audit events, and user notification.

If recoverable passwords or reset tokens are exposed, preserve evidence, disable the disclosure path, revoke outstanding recovery tokens and sessions as risk warrants, rotate related keys, force affected password resets, and notify users under the incident and legal plan. Do not roll back to plaintext retrieval to relieve support pressure; temporarily strengthen assisted recovery and staffing instead. Safer recovery adds operational cost, but recoverable passwords externalize that cost as systemic breach risk.

Attribution and verification

This guide was prompted by Shane’s Stack Overflow question and Michael Burr’s accepted answer, used under CC BY-SA. The answer’s key insight—provide new access rather than retrieve the old secret—remains valid; its suggested generated passphrase is not a substitute for today’s tokenized reset design. Current storage parameters were independently verified against OWASP’s Password Storage Cheat Sheet, and the full recovery flow against OWASP’s Forgot Password Cheat Sheet. Recovery-channel lifetimes and modern verifier requirements were checked against NIST’s current SP 800-63B-4.

Primary source: Review the official reference ↗