This playbook walks through moving an Auth0 tenant to theauth-go. The process
is two stages: export (convert the Auth0 data to the intermediate bundle format)
then apply (write the bundle to your theauth-go storage backend).
The two-stage design lets your team audit the JSON before committing to any
production writes.
Prerequisites
theauth-migrate binary (build with go build ./cmd/theauth-migrate)
- Auth0 Management API access token with
read:users scope
- Postgres DSN for your theauth-go database
theauth-go configured with PasswordPolicy.AllowLegacyBcrypt = true
during the migration window (see step 8)
Key difference from Cognito
Auth0 database-connection users have bcrypt password hashes that are
exportable. The migration tool preserves these hashes in the bundle. When a
user logs in after migration, theauth-go detects the bcrypt hash, verifies the
password using bcrypt, and on success transparently re-hashes with Argon2id.
The user never needs to reset their password.
This is the standard “hash migration” pattern. It requires the
AllowLegacyBcrypt config flag during the transition window (typically 30-90
days). Disable it once the vast majority of active users have logged in and
had their hashes upgraded.
Step 1: Export users from Auth0
Option A: Auth0 Management API (recommended for smaller tenants)
Option B: Auth0 bulk export with password hashes
For password hash export you need the Auth0 Management API bulk export job,
which requires the tenant to be on a paid plan and the
read:user_custom_blocks or equivalent scope.
Note: the custom_password_hash field is the bcrypt hash for database
connection users. Rename or map it to password_hash if needed before
running the migrate tool.
Option C: Auth0 export-users extension
If you have the “Export Users to CSV” or “User Import / Export Extension”
installed in your Auth0 dashboard:
- Open your Auth0 dashboard.
- Go to Extensions and click the export extension.
- Select “Export” and choose JSON format.
- Download the file.
The extension output matches the Management API format.
To force a password reset for all users (ignoring bcrypt hashes):
Step 3: Inspect the bundle
Open bundle.json in your editor. Key things to review:
notes: human-readable caveats. Important items:
AllowLegacyBcrypt must be enabled in theauth-go during the migration window.
- Auth0 rules and hooks are out of scope.
passwords: users with bcrypt hashes. Verify the count matches the number
of database-connection users in your tenant.
oauth_accounts: social connections (Google, GitHub, etc). Verify provider
names are correct.
mfa_enrolled: users who had Guardian MFA. They must re-enroll TOTP.
Social connection provider mapping
Auth0 uses provider names like google-oauth2, github, facebook. The
migrate tool normalizes these to the conventional short names used by
theauth-go (google, github, facebook). You can verify the mapping in the
oauth_accounts array of the bundle.
Auth0 rules and hooks
Auth0 rules and hooks run as JavaScript functions on every authentication
event. They are out of scope for the migrate tool. Common patterns to
re-implement in theauth-go:
- User enrichment (adding claims): use a custom token hook or middleware.
- IP blocking: use the theauth-go rate-limiter config or middleware.
- Progressive profiling: implement in your application layer.
- Custom MFA: use theauth-go TOTP or WebAuthn.
Step 4: Validate the bundle
Fix any reported errors before proceeding.
Step 5: Enable legacy bcrypt support in theauth-go
During the migration window, add the following to your theauth-go config:
The library persists the new hash itself, so the callback is optional. Replace
mirrorStore with whatever holds your own copy of the hash. The callback runs
in a separate goroutine; the user’s login is not delayed.
Step 6: Dry-run apply
Step 7: Apply to production
The applier:
- Validates the bundle.
- Checks for existing users by email (idempotent).
- Inserts users in batches of 500.
- Sets password hashes (bcrypt or Argon2id) for users who have them.
- Inserts OAuth accounts for social connections.
- Prints a list of emails that need password-reset tokens (only for users
with no hash or when
--force-password-reset is used).
Step 8: Monitor the hash upgrade
After the migration, monitor how many users still have bcrypt hashes versus
Argon2id hashes. You can query your storage to see how many rows in the
password hash column start with $2b$ vs $argon2id$.
Once the vast majority of active users have logged in (typically after 30-90
days), disable the bcrypt fallback:
Any remaining users with bcrypt hashes will need to reset their password. You
can identify them by querying for rows starting with $2b$ and sending
reset emails proactively.
Step 9: Update your application
- Update your sign-in flow to point at theauth-go.
- Update your token validation (if you used Auth0 JWTs, update your RS256
public key to the theauth-go JWKS endpoint).
- Re-implement Auth0 rules and hooks as described in step 3.
- Map
app_metadata and user_metadata to your application’s data model.
They are preserved in the bundle’s user metadata field with app: and
user: prefixes respectively.
Step 10: Cutover and cleanup
- Test that users can log in via theauth-go.
- Disable Auth0 login in your application.
- After the hash upgrade window closes, disable
AllowLegacyBcrypt.
- After 30 days, suspend your Auth0 tenant.
Rollback plan
- Point your application back at Auth0.
- Optionally drop and re-populate the theauth-go tables.
Auth0 is unchanged by this migration; rollback is safe at any point before
you suspend the Auth0 tenant.
Common issues
The export file is not valid JSON. Common causes:
- The Management API returned an error response (check for
"statusCode" in
the file).
- The bulk export was incomplete. Re-run the export job.
- The ndjson file was not converted to a JSON array. See step 1 option B.
bcrypt hashes not appearing in the bundle
The password_hash field is only present in bulk export jobs. The standard
/api/v2/users endpoint does not return password hashes. Use option B in
step 1 to get hashes.
Social connections not appearing in oauth_accounts
Only connections with "isSocial": true are mapped. Enterprise connections
(SAML, LDAP, Azure AD) are not mapped because theauth-go does not have a
direct equivalent; use theauth-go SAML or OIDC instead.
Users are missing from the export
Auth0 paginates users in alphabetical order by user ID. If the export was
interrupted, restart from the beginning; duplicate detection in the applier
will skip already-imported users. Last modified on October 7, 2026