WorkOS Migration: Auth0
Step 1: Fetch Documentation (BLOCKING)
STOP. Do not proceed until complete.
WebFetch: https://workos.com/docs/migrate/auth0
The official migration guide is the source of truth. If this skill conflicts with the docs, follow the docs.
Step 2: Pre-Migration Assessment
Inventory Current Auth0 Setup
Run these checks in Auth0 Dashboard or Management API:
- •Count total users: Check Users section or GET
/api/v2/users - •List authentication methods: Password, social (Google, Microsoft, etc.), enterprise SSO
- •Check organizations: Count Auth0 Organizations and membership mappings
- •MFA status: Identify users with SMS MFA (requires migration)
Document findings before proceeding.
WorkOS Prerequisites Validation
# 1. Check environment variables exist grep -E "WORKOS_API_KEY|WORKOS_CLIENT_ID" .env.local || echo "FAIL: Missing WorkOS keys" # 2. Verify API key format grep "^WORKOS_API_KEY=sk_" .env.local || echo "FAIL: Invalid API key format" # 3. Check WorkOS SDK installed npm list @workos-inc/node || yarn list --pattern @workos-inc/node
All checks must pass before export.
Step 3: Export Auth0 User Data
Export User Profiles (REQUIRED)
Method: Use Auth0's User Import/Export Extension
- •Navigate to Auth0 Dashboard → Extensions
- •Install "User Import / Export" extension if not present
- •Click "Export Users" → Select target connection
- •Download NDJSON file (newline-delimited JSON)
Verify export:
# Check file exists and is valid JSON-per-line cat auth0_users.ndjson | head -1 | jq . || echo "FAIL: Invalid JSON format" # Count exported users wc -l < auth0_users.ndjson
Export Password Hashes (CONDITIONAL)
Only if: Users authenticate with passwords (not just social/SSO)
BLOCKING STEP: Contact Auth0 Support to request password hash export. This takes 5-10 business days.
Support ticket must include:
- •Tenant domain
- •Connection name(s) needing password export
- •Justification: "Migrating to WorkOS AuthKit"
Auth0 provides separate NDJSON file with passwordHash field (bcrypt format).
Verify password export:
# Check password hashes exist in export cat auth0_passwords.ndjson | head -1 | jq -r '.passwordHash' || echo "FAIL: No password hashes"
Step 4: Data Mapping (Decision Tree)
User data export ready?
|
+-- Passwords included
| |
| +-- Import with password_hash (Step 5A)
|
+-- Passwords NOT included
| |
| +-- Users must reset passwords via Magic Auth
|
+-- Social auth users (Google, Microsoft, etc.)
|
+-- Configure OAuth providers in WorkOS first (Step 6)
+-- Import users without passwords
+-- Auto-link on first sign-in
Field Mapping Reference
| Auth0 Field | WorkOS API Parameter |
|---|---|
email | email |
email_verified | email_verified |
given_name | first_name |
family_name | last_name |
passwordHash | password_hash |
Step 5: Import Users into WorkOS
Option A: Using WorkOS Import Tool (RECOMMENDED)
# 1. Clone migration tool git clone https://github.com/workos/migrate-auth0-users.git cd migrate-auth0-users # 2. Install dependencies npm install # 3. Set environment variables export WORKOS_API_KEY="sk_..." export AUTH0_USERS_FILE="./auth0_users.ndjson" export AUTH0_PASSWORDS_FILE="./auth0_passwords.ndjson" # Optional # 4. Run import (dry-run first) npm run import -- --dry-run # 5. Execute actual import npm run import
Verify import:
# Check WorkOS Dashboard user count matches Auth0 export # Expected: User count in WorkOS = line count in NDJSON
Option B: Custom Import Script
If writing custom import code, use WorkOS SDK:
// Example: Import single user with password
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const user = await workos.userManagement.createUser({
email: auth0User.email,
email_verified: auth0User.email_verified,
first_name: auth0User.given_name,
last_name: auth0User.family_name,
password_hash: auth0User.passwordHash, // bcrypt format
password_hash_type: 'bcrypt', // REQUIRED when password_hash provided
});
CRITICAL: Set password_hash_type: 'bcrypt' when importing Auth0 passwords. This is Auth0's hashing algorithm.
Error handling pattern:
try {
await workos.userManagement.createUser({...});
} catch (error) {
if (error.code === 'user_already_exists') {
// Update existing user instead
await workos.userManagement.updateUser(userId, {...});
} else {
throw error;
}
}
Step 6: Configure Social Auth Providers
Only if: Users sign in with Google, Microsoft, GitHub, etc.
Provider Setup Checklist
For each social provider in Auth0:
- •
In WorkOS Dashboard:
- •Navigate to Authentication → Social Connections
- •Enable provider (e.g., Google OAuth)
- •Add OAuth client credentials from provider console
- •
Provider configuration:
- •Add WorkOS callback URL to provider's allowed redirects
- •WorkOS callback:
https://api.workos.com/sso/oauth/callback
- •
Email verification settings:
- •Check WorkOS environment settings
- •Google/Microsoft users with matching domain may skip verification
- •Other providers require email verification if enabled
Verify provider:
# Test sign-in flow in WorkOS Dashboard # Authentication → Social Connections → [Provider] → Test Connection
Auto-Linking Behavior
When social auth users sign in via WorkOS:
- •WorkOS matches by email address
- •If imported user has matching email, automatically links accounts
- •User sees seamless sign-in experience
No code changes needed — linking is automatic.
Step 7: Migrate Organizations (CONDITIONAL)
Only if: Using Auth0 Organizations feature
Export Auth0 Organizations
// Use Auth0 Management API to export orgs
const auth0 = new ManagementClient({
domain: 'your-domain.auth0.com',
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
});
const organizations = await auth0.organizations.getAll();
Create WorkOS Organizations
const workos = new WorkOS(process.env.WORKOS_API_KEY);
for (const auth0Org of organizations) {
const workosOrg = await workos.organizations.createOrganization({
name: auth0Org.name,
domains: auth0Org.domains?.map((d) => d.name) || [],
// Optional: Store Auth0 org ID for reference
externalId: auth0Org.id,
});
// Add organization memberships
for (const member of auth0Org.members) {
await workos.userManagement.createOrganizationMembership({
organization_id: workosOrg.id,
user_id: workosUserId, // Mapped from Auth0 user ID
});
}
}
Verify organization import:
# Check WorkOS Dashboard: Org count matches Auth0 # Check user memberships in WorkOS match Auth0 export
Step 8: Handle MFA Migration
SMS MFA Users (BREAKING CHANGE)
WorkOS does not support SMS MFA due to security vulnerabilities.
Users with SMS MFA must:
- •Re-enroll in MFA using TOTP authenticator app (Google Authenticator, 1Password, etc.)
- •OR switch to email-based Magic Auth (passwordless)
Communication Plan
- •Email users with SMS MFA before migration
- •Provide enrollment instructions: Link to WorkOS MFA setup guide
- •Offer grace period where MFA is optional
- •Enforce MFA after grace period
Verification Checklist (ALL MUST PASS)
# 1. User count matches echo "Auth0 users: $(wc -l < auth0_users.ndjson)" echo "WorkOS users: [Check Dashboard]" # Expected: Counts match within margin of error # 2. Password authentication works # Test: Sign in with email/password in your app # Expected: Users can authenticate with existing passwords # 3. Social auth configured # Test: Sign in with Google/Microsoft # Expected: Users auto-link to existing accounts # 4. Organizations migrated (if applicable) # Check: WorkOS Dashboard → Organizations # Expected: Org count matches Auth0 # 5. MFA status # Check: Users with TOTP MFA can sign in # Expected: SMS MFA users prompted to re-enroll # 6. Application builds npm run build
Error Recovery
"user_already_exists" during import
Cause: User with email already exists in WorkOS (likely from test imports)
Fix: Use Update User API instead of Create:
await workos.userManagement.updateUser(existingUserId, {
first_name: auth0User.given_name,
password_hash: auth0User.passwordHash,
password_hash_type: 'bcrypt',
});
"invalid_password_hash" error
Cause: Wrong password_hash_type or malformed hash
Fix:
- •Verify
password_hash_type: 'bcrypt'(Auth0's algorithm) - •Check password hash format: Should start with
$2a$,$2b$, or$2y$ - •Ensure hash is full string from Auth0 export (no truncation)
Social auth users cannot sign in
Cause: Provider not configured or callback URL mismatch
Fix:
- •Check WorkOS Dashboard → Authentication → Social Connections → [Provider] is enabled
- •Verify OAuth credentials are correct
- •Add WorkOS callback URL to provider's allowed redirects:
https://api.workos.com/sso/oauth/callback - •Test connection in WorkOS Dashboard
Users not auto-linking to social accounts
Cause: Email mismatch or email verification required
Fix:
- •Verify imported user email matches social provider email exactly
- •Check email verification settings in WorkOS environment
- •If verification required, user must verify email before linking
Organization memberships missing
Cause: User IDs not mapped correctly from Auth0 to WorkOS
Fix:
- •Store Auth0 user ID → WorkOS user ID mapping during import
- •Use mapping when creating organization memberships
- •Double-check
user_idparameter increateOrganizationMembershipcalls
Migration taking too long (>1 hour for 10k users)
Cause: Serial API calls, no batching/parallelization
Fix:
- •Use Promise.all() with batches of 50-100 concurrent requests
- •Add retry logic with exponential backoff
- •Consider using WorkOS bulk import tool (handles rate limiting)
Post-Migration Tasks
- •Update application code: Replace Auth0 SDK calls with WorkOS AuthKit SDK
- •Test authentication flows: Password, social, MFA, password reset
- •Monitor error logs: Check for authentication failures in first 48 hours
- •Deprecate Auth0: After validation period, disable Auth0 tenant
- •Update documentation: Internal docs, onboarding guides, support articles
Related Skills
- •
workos-authkit-nextjs- Integrate WorkOS AuthKit with Next.js after migration - •
workos-sso-setup- Configure enterprise SSO connections in WorkOS - •
workos-organizations- Manage organizations and memberships programmatically