Prompt and context
A signed-in user submits a new email address. The service must prove control of the new address while reducing account-takeover risk from a stolen session, email enumeration, repeated clicks, and concurrent requests. Explain the data model, token lifecycle, notification, session policy, rate limits, audit events, and recovery path.
Assume that changing an email affects login, password recovery, and security notices, so it is a high-risk account action. The current session may be stolen, either address may be temporarily unavailable, and only one change request may be active for an account.
What the interviewer tests
The interviewer wants to hear a distinction between an active session and recently re-proven identity. Strong answers require reauthentication or an existing multi-factor method for high-risk accounts. They keep the proposed address separate until proof succeeds.
They also look for a single-use random token, short lifetime, server-side token digest, replay prevention, old-address notification, uniform errors, and audit events. Excellent answers cover session revocation, recovery, and invariants for concurrent updates.
30-second answer
“I would require recent reauthentication, and an existing MFA method for higher-risk accounts. I would store the proposed address in a separate pending record rather than replacing the current one. A cryptographically random, single-use token would be emailed to the new address; the database would keep only its digest, purpose, expiry, and consumed state. The confirmation transaction locks the account, checks the digest, expiry, and version, then atomically changes the address and consumes the request. The old address receives a security notice and cancellation option. Sessions are revoked or stepped up according to risk. Responses are enumeration-safe, requests are rate-limited, and audit events never contain the token.”
Step-by-step deep dive
Step 1: Model the account and pending request
Keep current_email on the account and store a separate pending_email_change record containing account ID, normalized proposed address, token digest, purpose, expiry, state, version, creation time, and the initiating session’s risk context. Until proof succeeds, the proposed value must not affect login or recovery.
Step 2: Reauthenticate and set the risk boundary
An active login only proves that the session is accepted. Require a recent password check or an existing MFA factor, and consider device, IP, and anomaly signals. Knowing the new address does not prove current identity. The step-up result should be short-lived and single-purpose, not a durable universal credential.
Step 3: Generate and deliver a one-time token
Use a cryptographically secure random generator, a short TTL, and one purpose. Store only a one-way digest in the database; the email link carries the raw token. The message should not reveal whether an account exists. Resends are throttled and invalidate older tokens. Consumed, expired, or superseded tokens cannot be reused.
Step 4: Verify and commit atomically
On confirmation, perform format and rate checks, then lock the account and pending row in one transaction. Compare the digest in constant time and check state, TTL, purpose, and version. Enforce new-address uniqueness in the same transaction, atomically update the current email, consume the request, and append an audit event. A duplicate click returns an idempotent or safe processed result without repeating side effects.
Step 5: Handle the old address, sessions, and notices
Send the old address a security notice and allow cancellation while the request is still pending. On success, revoke long-lived refresh tokens or require other sessions to reauthenticate; high-risk accounts may require immediate global revocation. The new address must not become the sole instant recovery proof.
Step 6: Control enumeration, abuse, and races
Use uniform timing and messages for request and confirmation endpoints, so attackers cannot learn whether an address is registered. Rate-limit by account, target address, device, and network. A uniqueness constraint plus row locking or conditional version update prevents two requests from succeeding; the occupancy check and write share one transaction boundary.
Step 7: Design recovery and unavailable cases
Permit limited resends for delivery delays while invalidating old tokens. If the old address is lost, do not accept an unverified new address as sufficient takeover proof; use the account’s stronger recovery process. Delivery failures, occupied addresses, and policy denials should expose safe messages while retaining internal reason codes in audit records.
Step 8: Test threats and measure outcomes
Test stolen sessions, token replay and expiry, concurrent confirmations, old-address cancellation, refresh-token revocation, cross-account address ownership, and endpoint enumeration. Measure step-up failures, replay attempts, confirmation-to-revocation latency, notification delivery, and manual recovery volume. A database leak test should show that stored digests are not usable tokens.
Trade-offs, boundaries, and information gain
A short TTL reduces exposure but increases resend pressure when mail is delayed; bounded resends and explicit status balance the two. Revoking every session is safer but interrupts multiple devices, so risk tiers can choose the scope. An old-address cancellation window improves recovery but cannot undo a completed high-risk change.
Digest-only storage protects against database disclosure, but raw tokens can still leak through links, browser history, logs, tracing, and analytics. Filter those paths and redirect after consumption to a clean URL. A uniqueness constraint simplifies identity ownership, but the product must define whether one address may belong to multiple accounts.
Model high-quality answer
“I would treat email change as a high-risk state machine. The user completes recent reauthentication and, when required, an existing MFA check. The proposed address goes into a pending record; the current address remains authoritative for login and recovery. A CSPRNG produces a single-use token with a short TTL, while the database stores only its digest, purpose, version, and state.
The confirmation transaction locks the account and request, checks the digest, expiry, version, and uniqueness constraint, then atomically updates the address, consumes the request, and records an audit event. The old address receives a security notice and can cancel a still-pending request. After success, refresh tokens are revoked and other sessions step up again according to risk.
All endpoints use uniform errors and rate limits by account, target, and network. Logs contain reason codes rather than tokens. Replay, races, lost old mailboxes, and delivery failures each have a defined recovery path, and tests measure token expiry, revocation latency, and enumeration resistance.”
Common mistakes
- Replace the address on form submission. An unproven address can lock out the user or let a stolen session take over.
- Trust the existing session alone. A session thief can complete a high-risk action.
- Store raw tokens in databases or logs. Those systems become takeover paths.
- Allow token reuse. Replay can repeat notifications or overwrite the address.
- Reveal address existence. Confirmation and request responses become an enumeration oracle.
- Ignore uniqueness races. Two accounts or requests may claim the same address.
- Keep every long-lived session after success. A stolen refresh token remains useful.
- Trust a new address when the old one is lost. This bypasses the account’s recovery assurance.
Follow-up questions and answers
Why not write the new address first and verify asynchronously?
Login, recovery, and notices may immediately consume it, while rollback introduces races. A separate pending value keeps unverified input outside the identity boundary.
How long should the token live?
There is no risk-independent number. Choose a short window from delivery distribution and threat goals, add bounded resends, and measure the actual issuance-to-expiry behavior.
Can an old-address notice replace MFA?
No. Notice and cancellation are defense-in-depth and recovery signals, not proof of a high-assurance identity. High-risk accounts still need an existing MFA method or stronger recovery.
What if two tabs confirm simultaneously?
Use a version, one-time state, and transaction lock so only one request transitions from pending to consumed. The second request returns an idempotent result and performs no duplicate side effects.
How do you keep tokens out of analytics?
Use a one-time path, filter query parameters at the edge and application layers, redirect after consumption to a clean URL, and disable caching for sensitive responses.