Implementation worksheet · 6 min read
A User Account Migration Checklist for a Rebuilt App
Move accounts in four separable pieces, because each has different rules: identity (the user record and its identifier), credentials (password hashes, which migrate only if the algorithms are compatible), authorisation (roles and permissions, which usually need remapping rather than copying), and state (sessions, consent, preferences and anything with a legal basis). Never re-hash passwords you cannot verify and never store them in a recoverable form during the move. If hashes cannot transfer, the fallback is a rehash-on-next-login shim, not a mass password reset — a reset email to your entire user base looks exactly like a breach notification and will be treated as one.
Rebuilding an application usually means a new auth implementation, and accounts are the part users notice immediately. A migration that loses a permission is an annoyance; one that forces a password reset for everyone costs trust and generates support volume for weeks, and it is the default outcome when hashes turn out to be incompatible late in the project.
Put it into practice
1. Check password hash compatibility first, not last
The algorithm, cost factor and encoding of the existing hashes, against what the new system supports. Do this in week one, because the answer changes the entire migration plan. If the new system can verify the old hashes, everything else is straightforward; if not, you need the shim below and time to build it.
2. If hashes cannot transfer, build a rehash-on-login shim
Keep the old hashes, verify against them on next login, and immediately re-hash with the new algorithm and discard the old. Users notice nothing. This spreads the migration over natural logins and avoids a mass reset — it is more work than sending an email and it is the difference between a silent migration and a support incident.
3. Preserve the user identifier, or map it explicitly
Every foreign key, audit record and external integration references the user by id. Changing identifiers means updating everything that points at them, and anything you miss becomes an orphaned record. If ids must change, keep a mapping table permanently, not just for the migration.
4. Remap permissions rather than copying them
Role names rarely mean the same thing in a rebuilt system. Write the old-to-new mapping explicitly and have someone who knows the business check it — particularly for anyone with elevated access. The failure that matters is silently granting more than before, and it is invisible unless someone compares.
5. Carry consent and marketing preferences exactly
Opt-outs, communication preferences and anything with a legal basis migrate as-is, and where a value is ambiguous, the most restrictive interpretation wins. Losing an unsubscribe during a migration means mailing someone who opted out, which is a compliance problem rather than a data problem.
6. Decide what happens to sessions
Sessions almost never transfer. Everyone is logged out at cutover — decide whether to tell them in advance. A short notice beforehand turns a confusing forced logout into an expected one, and costs one email to people who are already your users.
7. Test with real-shaped edge cases
The account with two roles, the one with a very old hash format, the one with a deleted-but-referenced record, the one with unicode in the name, the one belonging to a user who left. Ten deliberately awkward accounts find more than a thousand ordinary ones.
8. Run a verification pass after import, before cutover
Counts match, a sample of users can authenticate against migrated hashes in a staging environment, permissions match the mapping, and nobody gained access they did not have. Write the result down before the cutover decision, not after.
Account migration checklist
Copy this structure into your review document and record your observed result for each row.
| Item | Approach | Verified |
|---|---|---|
| Password hash compatibility | checked in week one | |
| Rehash-on-login shim | built if hashes cannot transfer | |
| Mass password reset | avoided — reads as a breach notice | |
| User identifier | preserved, or mapped permanently | |
| Roles and permissions | remapped and reviewed | |
| Elevated access accounts | individually checked | |
| Consent and marketing preferences | carried exactly, restrictive wins | |
| Sessions | expire at cutover, users notified | |
| Two-factor enrolment | migrated or re-enrolment planned | |
| Deleted and deactivated users | state preserved, not reactivated | |
| Edge-case accounts tested | ten deliberately awkward ones | |
| Post-import verification | counts, auth sample, permission diff |
A failure worth checking
The mass password reset. Hashes turn out to be incompatible a week before cutover, the fastest fix is emailing every user a reset link, and it goes out on migration day. To a recipient this is indistinguishable from the email a company sends after a breach — unexpected, urgent, and asking them to change their password. Support fills with people asking whether they have been hacked, a portion never complete the reset and are effectively churned, and the explanation never fully catches up with the first impression. Checking hash compatibility in week one is what prevents it.
Common questions
Can we ever ask everyone to reset their password?
Only with genuine advance notice, a clear reason and a deadline — and expect a meaningful share never to return, because a forced reset is a churn event as much as a security one. It is a last resort rather than a migration strategy.
What about two-factor enrolment?
Depends on the mechanism. Time-based secrets can often move; hardware keys and platform authenticators are usually bound to the old implementation and need re-enrolment. Find out before cutover, and where re-enrolment is needed, tell affected users specifically rather than issuing a general notice.
Basis and scope
This is a proposed implementation method using illustrative examples, not a measured benchmark or a customer case study. Prepared with AI assistance. Validate product-specific behavior against current documentation and your own test environment.