Migrating to LumoAuth
LumoAuth ships a one-command migration path for Auth0 (lumo migrate auth0), plus guided manual migrations for Clerk, Okta, Cognito, and Firebase that use the Admin API directly. Each guide explains:
- What gets migrated (users, organizations, groups, roles, SSO connections)
- What gets dropped and why (passwords, vendor-specific session state, MFA enrolment)
- How to do the migration with zero downtime (parallel-run pattern)
Pick your source platform
| From | Guide | Method |
|---|---|---|
| Auth0 | Migrating from Auth0 | lumo migrate auth0 --file users.json |
| Clerk | Migrating from Clerk | Export, reshape with jq, create users through the Admin API |
| Okta | Migrating from Okta | Export, reshape with jq, create users through the Admin API; or keep Okta as the upstream identity provider |
| AWS Cognito | Migrating from Cognito | Export with the AWS CLI, reshape with jq, create users through the Admin API |
| Firebase Auth | Migrating from Firebase | Export with the Firebase CLI, reshape with jq, create users through the Admin API |
The manual guides all use the same endpoint the Auth0 importer calls, POST /orgs/{orgId}/api/v1/admin/users, one user per request. Call it with curl, with lumo api POST /users --data '{…}', or use lumo users create for its flag-based subset (--email, --name, --given-name, --family-name, --username, --password, --phone, --roles, --groups, --verified).
What every migration preserves
Every guide carries over:
- The user's email, email verification state and name fields
- Group / organization memberships (for sources that have them)
The Admin API's create endpoint stores email, username, name, givenName, familyName, phoneNumber, password, isActive, emailVerified, roles and groups. It accepts a metadata object as well, but user responses do not return it, so do not rely on it to carry the vendor's user id. Instead, keep your own mapping from the vendor id (auth0|abc123, user_2abc…, 00u1abc…) to the LumoAuth user id returned in the create response (data.id); each guide's import loop records it. App code that hard-coded the old id looks the user up through that table.
LumoAuth assigns each migrated user a fresh createdAt at import time; the create endpoint does not accept a caller-supplied creation timestamp. Keep the vendor's original timestamp in your own mapping table if your app relies on it.
What every migration drops
- Password hashes. bcrypt costs, peppers, and breach-check flags don't survive the migration cleanly. Migrated users go through your organization's first-login flow (magic link / password reset / SSO).
- Active session state. Old refresh tokens are not honored — users sign in fresh.
- Vendor-specific MFA enrolment. TOTP seeds (authenticator-app secrets) and passkeys are bound to the source vendor's domain; new enrolment is required on LumoAuth. With the default Risk-based requirement users re-enroll from Sign-in & security; set the MFA policy to Optional to invite them after every sign-in, or Required with a grace period to make it mandatory (see MFA).
Parallel-run pattern (zero-downtime)
For production migrations, run both providers side-by-side until you've cut over:
- Replicate users. For Auth0, run
lumo migrate auth0 --file users.jsonagainst an export — idempotent, so it's re-runnable as more users sign up on the source. For Clerk, Okta, Cognito, and Firebase, follow that guide's import loop (same idempotent, re-runnable behavior: a409 Conflictmeans the user already exists). - Dual-issue tokens. Your app accepts tokens from either provider. Tag each user with
migration_sourcein your own database so audit logs make sense. - Migrate logins. When a user signs in on the source, prompt them to sign in to LumoAuth (magic link). Once they have, mark
migrated_to_lumoauth=trueon your user record. - Cut over. When ≥ 95% of monthly-active users have migrated, flip the default to LumoAuth. The remaining 5% migrate on next sign-in.
- Sunset the source. After 90 days of no traffic, decommission.
Migration support
Migrations of more than 10K users? Email migrate@lumoauth.dev. We'll dry-run your export, surface any data-shape issues, and pair on the cutover.