Migrating from Auth0
Auth0 is the easiest source to move: its user export carries the bcrypt password hashes, so users keep their password, and ids can be kept as the token sub. The Auth0 compatibility profile takes care of how SDKs and APIs talk to the service.
What moves, and what does not
| In Auth0 | In auth-service | Notes |
|---|---|---|
| Application (regular web, SPA, native) | Application (confidential or isPublic) | Created again |
| Machine-to-machine application | Confidential application | client_credentials, scope m2m |
| API and its identifier (audience) | Application url | audience is accepted as resource with the profile |
| Users | Users, with their auth0|… id as id | sub unchanged |
| Passwords (bcrypt) | Imported hashes, replaced by native ones at the first sign-in | Needs the export with hashes |
| Roles and permissions | Roles; permissions per role | permissions claim is native |
| Actions, Rules, hooks | — | No equivalent: custom claims use claim mappings |
| MFA | TOTP with backup codes | Users enroll again |
| Social connections | Google and GitHub | |
| Organizations | Organizations | Recreate and add members |
1. Configure the instance
COMPAT_PROFILE=auth0
For all applications of the instance:
- confidential clients authenticate with
client_secret_post(the secret in the body), Auth0's default; - tokens are signed with RS256;
audienceis accepted asresource; without either, the access token is opaque, as with Auth0;/authorize,/oauth/token,/oauth/revoke,/userinfoand/.well-known/jwks.jsonanswer like the canonical endpoints, so a tenantdomainsetting can point to the service.
Auth0 puts roles in a namespaced claim. Add yours if your APIs read it, for example:
CLAIM_MAPPINGS=[{"claim":"https://acme.example/roles","source":"roles","scope":"roles"}]
Set the profile before the first start. Details: Compatibility profiles.
2. What still changes in an application
| Auth0 | auth-service | |
|---|---|---|
| Domain / issuer | https://<tenant>.auth0.com/ | https://auth.example.com (no trailing slash) |
client_id | Generated | The application slug |
| Audience | audience of the API | audience is accepted as resource (the profile turns it on); aud is that resource, which must be the application's own url or the url of another application listed in its allowedResources. With no audience, the token is opaque |
auth0-spa-js | JSON or form token requests | Form requests only: keep the default useFormData |
| Logout | /v2/logout?returnTo= | Local sign-out in the app, or end-session (global, off by default per application) |
sub | auth0|… | Unchanged |
The issuer of tokens is the root URL: configure your API with https://auth.example.com, not with the Auth0 form.
3. Move the users
Export the users from Auth0 with their password hashes (a bulk export; the hashes need a support request on Auth0's side), then:
# Dry run
pnpm migrate:auth0 -- --file users.ndjson --app acme-app
# For real
AUTH_SERVICE_URL=https://auth.example.com ADMIN_EMAIL=… ADMIN_PASSWORD=… \
pnpm migrate:auth0 -- --file users.ndjson --app acme-app --apply
The file is a JSON array or one JSON object per line. The command reads user_id, email, email_verified, name (or nickname), passwordHash (a bcrypt hash) and app_metadata.roles; roles become the application's roles. Users without an email are skipped. Options (--role-priority, --trust-email, --send-verification, --no-keep-ids, --report), the environment variables, the superadmin requirement and the report are those of the Okta command; --trust-email is true by default here and marks as verified the addresses that the export says are verified (email_verified).
Users without a hash (social-only accounts, for instance) get a random temporary password that is not shown or reported: they set their own with Forgot your password?. Users with a hash keep their password, which is re-hashed natively at their first sign-in. Delete the export file when you are done: it contains password hashes.
4. Cut over
Accept both issuers in your APIs while users move, as shown in the Okta guide: sub is the same on both sides. Then switch the sign-in side one application at a time, watch the login history, and retire Auth0 when no traffic remains.
Migrating from Okta
Move an application and its users from Okta to auth-service with the Okta compatibility profile and the migrate:okta command, then cut over with both issuers accepted.
Migrating from Keycloak
Move users and applications from Keycloak to auth-service with the Keycloak compatibility profile and the migrate:keycloak command, keeping PBKDF2 passwords.