How to Change a User’s Email Address Safely
A stolen session should not be able to replace an account’s recovery address in one request. The safe rule is to keep the current email active, store the proposed address as a pending change, re-authenticate the user, confirm the new inbox, and commit the change only after every required proof succeeds.
If your application overwrites users.email before confirmation, the attacker can receive password resets and account notices. Sending a “Was this you?” message to the old address does not undo that mistake.
The old address must survive the pending state
I would model an email change as a state transition, not a profile-field edit. The account’s stable internal user ID stays fixed, the current address remains authoritative, and the proposal lives in a separate record until it expires, is cancelled, or commits.
ACTIVE(old@example.com)
|
| re-authenticate + propose new@example.net
v
PENDING(old@example.com, new@example.net, expires_at)
|\
| \ expire / cancel / report unexpected activity
| `------------------------------------------> ACTIVE(old@example.com)
|
| confirm required links
v
ACTIVE(new@example.net)
This boundary matters because an email address often doubles as a login name, recovery destination, notification route, and account lookup key. OWASP describes changing it as equivalent to changing identity, and recommends re-authentication, notification to the existing address, and confirmation of the new one.
Use a unique, immutable user ID for foreign keys and session subjects. If orders, audit events, subscriptions, or API tokens point at an email string, an address change becomes a data migration it never needed to be.
What changes when the user has MFA?
With MFA enabled, OWASP’s recommended flow is to validate the session, require the second factor, store the proposal, notify the current address, and require confirmation at the new address. The current-address message should include a way to report an unexpected request.
Without MFA, a valid session is weaker evidence. OWASP recommends asking for the current password and requiring confirmation from both the current and proposed addresses. That extra current-address confirmation is inconvenient, but the objection is fair only until you consider the failure it blocks: anyone holding an unattended or stolen session could otherwise redirect account recovery.
Well, what if the user has lost access to the old inbox? That is an account-recovery problem. Route it through a separately designed recovery process with stronger evidence and support controls. Quietly skipping the old-address proof inside the normal change flow turns “I lost access” into a takeover shortcut.
A token should authorize one exact transition
Generate confirmation tokens with a cryptographically secure random source. Store only a hash of each token, give it one purpose, make it single-use, and expire it. Those properties follow OWASP’s ownership-verification guidance.
The pending record should bind the token to the user ID, the old address snapshot, the proposed address snapshot, the required proof, and the expiry time. Do not trust an email address supplied again in the confirmation request, and do not let a token issued for signup confirmation or password reset authorize an email change.
Only one proposal should be active per account. Creating a newer proposal can invalidate the older one, which makes concurrent browser tabs and repeated form submissions predictable. A cancellation or unexpected-activity report must also invalidate every remaining token for that proposal.
Where does email verification belong?
Verification belongs before you spend effort sending confirmation mail, but it does not replace confirmation. As I explain in email validation vs verification vs confirmation, verification evaluates address evidence while confirmation proves that someone currently controls the inbox.
You can send the proposed address to the emailverifier.dev API before creating the pending record. Treat a confirmed syntax or routing failure as a reason to ask for a correction. A disposable provider, role address, or similar flag is a risk signal that your own policy can review. An unknown result stays inconclusive, because a timeout, policy block, catch-all domain, or uncooperative mail server does not prove that the mailbox is invalid.
The product’s four statuses are deliverable, risky, undeliverable, and unknown. Keep that result beside the pending proposal rather than merging it with the “new inbox confirmed” flag. SMTP acceptance does not prove inbox ownership or guarantee later delivery.
When is it safe to commit?
Commit only after the server has reloaded the pending record and checked every condition again: it is still pending, it belongs to the authenticated account, it has not expired, the proposed address still matches the token-bound snapshot, every required confirmation is complete, and no other account owns the application’s comparison key.
Perform the uniqueness check, user update, pending-record transition, and audit event in one database transaction. Lock the user and proposal rows or use an equivalent compare-and-swap condition. Two valid links arriving together should produce one committed change, not two partially applied updates.
After commit, invalidate unused change tokens and any email-based recovery artifacts tied to the old address. Rotate or revoke sessions according to the risk of your application. OWASP also recommends token rotation after re-authentication, and the NIST subscriber-account guidance requires validated changes to core attributes in its identity-proofing scope, notification of updates, and a way to report unauthorized access.
What should the user see?
Before the change, show that the new address is pending while the old one remains active. After commit, send a clear notification to the old address and any other trusted notification channel your account supports. NIST notes that independent account-event notifications help subscribers detect fraud and should explain how to repudiate the event.
Do not place raw tokens, full confirmation URLs, or full email addresses in application logs. OWASP recommends masking or pseudonymizing email identifiers and never logging verification, reset, or authentication tokens.
Apply the same email comparison policy used by registration, login, reset, and account linking. The safe email storage model explains why lowercasing the domain is different from silently rewriting the local part.
The commit point is the whole design. Until every required proof passes, the account still belongs to the old address.