Browse the docs
Guides

Migrate from Clerk / Auth0

Move off your current identity provider without locking anyone out. This playbook covers exporting users and organisations, mapping fields, bulk import, migrating SSO, handling passwords and passkeys, and a phased cutover with no downtime.

Migrating identity is mostly a data problem plus a careful cutover. The data part is a bulk import. The careful part is that people are signing in the whole time, so you run both systems side by side for a window and switch traffic gradually. Done this way, no user has to re-register and there is no maintenance outage.

This guide assumes you already have an OrthID project and keys. If not, start with the Quickstart. For the product-level overview of replacing an incumbent IdP, see Replace your IdP.

1. Export users and organisations

Pull a full export from your current provider. From Auth0, use the bulk user export job or the Management API; from Clerk, use the backend API or a dashboard export. You want the complete user list plus every organisation and its memberships and roles.

  • Users: id, email, email-verified flag, phone, name, MFA enrolment, and the password hash if the provider exposes it.
  • Organisations: id, name, slug, and metadata.
  • Memberships: which user belongs to which organisation, and with what role.

2. Map fields

Translate the source schema to OrthID’s. Most fields map directly. Keep the source provider’s id in externalId so you can reconcile, deduplicate on re-runs, and trace a record back to its origin. Carry custom attributes into publicMetadata (readable by your app) or privateMetadata (server-only).

map.ts
// Map one Auth0/Clerk user record to an OrthID create payload.
function toOrthidUser(src) {
  return {
    externalId: src.user_id,            // keep the source id for reconciliation
    email: src.email,
    emailVerified: src.email_verified,
    phone: src.phone_number ?? null,
    name: src.name ?? src.nickname,
    // Carry an Auth0/Clerk password hash so logins keep working (see step 5).
    passwordHash: src.password_hash
      ? { algorithm: "bcrypt", value: src.password_hash }
      : undefined,
    publicMetadata: src.user_metadata ?? {},
    privateMetadata: src.app_metadata ?? {},
  };
}

3. Bulk import

Import with the server SDK using orthid.users and orthid.organizations. Import organisations first, then users, then attach memberships. The import is idempotent on externalId, so you can re-run it safely as you iterate, picking up new or changed records without creating duplicates.

import.ts
import { orthid } from "@orthid/sdk";
import { readFileSync } from "node:fs";
import { toOrthidUser } from "./map";

const data = JSON.parse(readFileSync("export.json", "utf8"));

// 1. Organisations first.
const orgIdByExternal = new Map();
for (const org of data.organizations) {
  const created = await orthid.organizations.create({
    name: org.name,
    externalId: org.id,
  });
  orgIdByExternal.set(org.id, created.id);
}

// 2. Users (idempotent on externalId, so re-runs are safe).
const userIdByExternal = new Map();
for (const src of data.users) {
  const user = await orthid.users.upsert(toOrthidUser(src));
  userIdByExternal.set(src.user_id, user.id);
}

// 3. Memberships, mapping source ids to the new OrthID ids.
for (const m of data.memberships) {
  await orthid.organizations.addMember({
    organizationId: orgIdByExternal.get(m.org_id),
    userId: userIdByExternal.get(m.user_id),
    role: m.role,
  });
}

console.log("Imported", data.users.length, "users");
Throttle and retry
Run the import in batches and respect rate limits. Because upsert keys on externalId, a failed batch is safe to retry: records that already imported are updated in place rather than duplicated.

4. Migrate SSO connections

Recreate each enterprise SSO connection in OrthID (SAML or OIDC) and point it at the same identity provider as before. Because OrthID matches federated users by verified email, an SSO user who signs in lands on the record you already imported, with their organisation membership intact. Update the connection’s ACS URL and entity ID at the customer IdP, or set them ahead of cutover if their IdP allows multiple service providers.

5. Passwords and passkeys

Credentials need the most care because they cannot all be exported.

  • Password hashes (bcrypt, scrypt, or argon2) can be imported directly, as in step 2. Users keep their existing password and never notice the move. OrthID verifies against the imported hash and transparently re-hashes on first successful login.
  • Passkeys cannot be exported. A passkey is bound to the origin it was created for, so it does not transfer between providers. Prompt those users to enrol a new passkey on first sign-in after cutover. Until they do, fall back to email magic link or their password.
  • MFA seeds (TOTP) transfer only if the source provider exposes them; most do not. Where they are unavailable, ask users to re-enrol MFA, and keep recovery codes valid during the window.
Run both systems in parallel
Do not flip every user at once. Keep the old provider live and run a dual-run period where new sessions are issued by OrthID while old sessions still validate against the previous provider. This protects you against any data you missed and lets you roll back instantly by routing sign-ins back to the old system.

6. Phased cutover without downtime

Switch traffic in stages rather than all at once. A typical sequence:

  1. Backfill: run the import while the old provider remains the source of truth. Re-run it close to cutover to capture last-minute changes.
  2. Shadow: verify sessions against OrthID for read-only checks while the old provider still handles sign-in. Compare results and fix mapping gaps.
  3. Canary: route a small percentage of sign-ins (or one internal organisation) to OrthID. Watch sign-in success, MFA, and SSO.
  4. Cut over: route all sign-ins to OrthID. Old sessions keep working until they expire; new ones are OrthID JWTs.
  5. Decommission: once old sessions have drained and metrics are clean, stop the dual-run and turn off the old provider.

Because sessions are short-lived JWTs, the old system can be retired naturally as tokens expire. There is never a moment where everyone is signed out.

Next steps

  • Replace your IdP for the business case and a migration checklist.
  • Enable SSO to configure each enterprise connection in OrthID.
  • Add MFA to set up factors for users who re-enrol after cutover.