# Migration

From [Descope Migration](https://learn.descope.io/c/migration) — Move an existing user base across without a hard cutover, or a forced password reset.

Take this course interactively at https://learn.descope.io/c/migration/migration

---

Most real migrations run both systems for a period. Coexistence is the design work that keeps that period from being visible to users, and it is where a migration that looked finished on paper meets the people using it.

## What a coexistence period has to handle

- Phased rollout — moving a slice of traffic at a time rather than all of it, so a problem affects some users instead of every user.
- Dual auth periods — both systems live at once, each authoritative for the users that have moved and the users that have not.
- Redirect handling — every callback and redirect target has to be valid in whichever system is handling that user, at the same time.
- Session continuity — deciding what happens to sessions the old system already issued.
- Post-cutover validation — confirming the new path works for real users before the old one is switched off.

The one that surprises people is session continuity, because it is the one that is not a configuration setting. A session issued by the legacy system is a token that system signed, and Descope has no reason to accept it — so the users holding one are not migrated in any meaningful sense until they sign in again. You can let those sessions drain naturally, which is invisible but slow and keeps the old system alive longer. Or you can invalidate them and accept that everybody signs in once on cutover day. Both are reasonable; neither happens by itself.

> [!WARNING]
> Redirect handling is where a phased rollout usually breaks first. During a dual auth period two systems are both sending users back to your application, so both sets of callback URLs and domain allowlists have to be correct simultaneously — and the Approved Domains list covered in Going to Production has to reflect the new world before any traffic reaches it, not after.

Post-cutover validation is worth planning as deliberately as the import. Sign in as a migrated user who has a password, one who authenticates socially, and one who has MFA enrolled; confirm roles and tenant membership survived; and check that a user who has not moved yet still gets through. The failure you are looking for is rarely a crashed login. It is a subset of users whose attributes did not come across, which looks like a working migration until somebody hits an authorization check.

## Key terms

- Phased rollout — moving traffic a slice at a time so a problem is contained.
- Dual auth period — the window where both systems are live, each authoritative for part of the user base.
- Session continuity — the decision about sessions the legacy system issued before cutover, which Descope cannot honour.

## Summary

- What a phased cutover involves, and why both systems stay live for a while
- That sessions do not migrate, and that letting them drain or cutting them off is a deliberate choice
- Why redirects and domain allowlists have to be right in both systems at once
- What post-cutover validation should actually exercise, and the failure it is looking for

---

Migrating to Descope means moving your existing users across without locking any of them out on the way. Whether you are coming from Auth0, Cognito, Firebase, a homegrown system, or just a table of password hashes, the first decision is which strategy fits — that choice shapes everything after it.

Full migration exports users, attributes, roles, and org mappings from the legacy provider, imports them via Batch Create User, then shifts traffic over. JIT migration skips the bulk export entirely: users are provisioned in Descope on first sign-in by verifying credentials against the legacy provider through a connector. Most projects combine both — bulk import the majority, JIT to catch stragglers. The right mix depends on whether the provider exposes password hashes, how large the user base is, and how much appetite there is for running both systems in parallel.

## Provider-specific quirks

- Auth0 — requires a support ticket for password hash exports; large tenants need JSON exports rather than paginated API calls.
- Cognito — does not expose password hashes at all, so JIT is the only path: a connector verifies credentials at sign-in, then Descope provisions the user.
- Firebase — has a dedicated migration tool that pulls hash parameters and copies custom attributes like UUIDs for downstream flow checks.
- Homegrown systems — export login IDs, emails, and hashes straight from the database and map to Batch Create User.

> [!WARNING]
> Confirm the password hashing algorithm early, whatever the provider. It is the single fact that determines whether passwords import directly or whether affected users need a reset flow — and discovering it late reshapes the whole plan.

## Key terms

- Full migration — exporting from the legacy provider and importing via Batch Create User, then shifting traffic.
- JIT (Just-in-Time) migration — provisioning on first sign-in by verifying credentials against the legacy provider, with no bulk export.

## Summary

- The two strategies, and why most projects use both
- Which provider forces JIT, and which needs a support ticket
- Why the hashing algorithm is the first thing to establish

---

Once the strategy is settled the work shifts to moving the data. Normalize your users into a consistent format — usually CSV or JSON — with a required login ID for each, plus optional email, phone, name, roles, and custom attributes.

Login IDs must be unique within the target project. Duplicates between source records, or collisions with users already in Descope, fail that individual import row rather than the whole batch. De-duplicate and validate before sending anything: check for missing required fields, malformed emails, and inconsistent role or tenant names, and run a small test batch before committing the full user base.

> [!NOTE]
> Validation errors from Batch Create User are reported per user by default, so a batch mixing valid and invalid records still creates the valid ones — unless the batch is explicitly configured to fail atomically.

Descope imports password hashes across bcrypt, PBKDF2, SHA variants, Django, MD5, and others, so migrated users sign in with the password they already have and never notice the move. TOTP secrets can be imported alongside user records too, preserving existing authenticator app enrollments so nobody re-scans a QR code.

Batch import is rate-limited, so chunk large migrations into smaller batches with delays between them rather than firing one enormous request and hoping. For rollback: keep the original export untouched, track which users were created per batch, and decide upfront whether failures get resolved by deleting and re-running or by patching individual records. Test that rollback on a small batch before the real migration.

## Key terms

- Login ID uniqueness — login IDs must be unique within the target project; collisions fail that row, not the batch.
- Rollback planning — deciding before the migration whether failures are resolved by delete-and-rerun or by patching records.

## Summary

- How to validate an export before you trust it
- That partial failure is per-row, not per-batch, by default
- Which hashes and secrets import directly, sparing users a reset
- How to stay inside rate limits, and what a tested rollback looks like

---
