Auth0 → Supabase
Export the Auth0 directory, ask support for the password hashes, and create those people in Supabase Auth. Sessions, organizations, and Actions stay behind.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Auth0
- Mature federation, extensibility, enterprise connections, and migration tooling cover complex identity programs
- Centralized sign-in, federation, MFA, and user lifecycle controls reduce application security work
- Tenant configuration, actions, pricing, and Auth0-specific claims can create substantial lock-in
- Passwords, sessions, factors, identities, and authorization policy are unusually sensitive to migration errors
Supabase
- PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack
- Managed infrastructure removes a large amount of operational work
- Production scaling and service integration still demand database and infrastructure judgment
- Platform-specific services can create architectural lock-in
Supabase: PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack. This removes a major source-side concern: Tenant configuration, actions, pricing, and Auth0-specific claims can create substantial lock-in.
What you lose: Mature federation, extensibility, enterprise connections, and migration tooling cover complex identity programs. What you inherit: Production scaling and service integration still demand database and infrastructure judgment.
Jump to a section
Know the shape of the move.
This timeline assumes
- The Auth0 tenant is on a paid plan. Bulk user export and the password-hash export are both unavailable on the free plan.
- You can open a support case. If Auth0 approves the hash export, a second tenant admin confirms it unless you are the only admin, and a CISO, CSO, or VP signs the form.
- Auth0 stays up through the rollback window. You can still turn its connections back on.
- The app can accept an Auth0 JWT and a Supabase JWT during the overlap. The site already documents Auth0 → Clerk, Auth0 → Keycloak, and Auth0 → Firebase Authentication.
- Firestore, storage, and the rest of a Firebase backend are a different move. That one is Firebase → Supabase.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Database passwords | partial | critical | The bulk export and the Management API do not include password hashes. Auth0 sends them only through a support case, PGP-encrypted, and not on the free plan. Supabase can take a bcrypt or argon2 hash on createUser. | Open the case before you write the import. Sign in as a test user whose password you know after the row lands. |
| Custom database connections | manual | critical | If the password lives in your database, Auth0 never stored the hash, unless you already ran a progressive migration into Auth0. | List every connection. Users with no hash need a reset, or the old login check stays up until they have signed in once. |
| Passwordless email and phone | partial | high | Supabase's guide says to create a confirmed user, then send the code with signInWithOtp. There is no password to copy. | Mark email_confirm or phone_confirm from Auth0's verified flag. Send one test code before you tell the list. |
| Social logins | partial | critical | signInWithOAuth does not need a pre-created row. The person signs in at Google or GitHub again. Auth0 stores that provider id as provider|id, for example github|123456. | Reuse the same OAuth client id and secret if you want to skip a fresh consent screen. Match the provider id to your existing app user. |
| Sessions and refresh tokens | lost | high | Supabase's rolling and one-off plans both say people may have to sign in again. An Auth0 refresh token is not a Supabase session. | Ship the new client first. Keep Auth0 able to sign people in until the fallback path is quiet. |
| MFA factors | manual | high | MFA secrets are a separate support export, same PGP path as the hashes. Supabase Auth only supports TOTP, and the migration guide says those people may need to enroll again. | Do not promise a silent MFA move. Have one TOTP user enroll on Supabase before cutover. |
| Organizations | lost | critical | Supabase's Auth0 guide says migrating Auth0 organizations is not supported. | Rebuild membership in your own tables, or pick a destination that already has orgs. Auth0 → Clerk is the other route on this site. |
| SAML connections | manual | critical | The customer has to attach the identity provider on the Supabase side. The guide tells you to contact Supabase support if you want the existing Auth0 SAML connections moved. SAML users are left out of automatic and manual identity linking. | Pilot one company before you tell the rest. Do not expect a shared email to merge a SAML user into a password user. |
| Actions, Rules, and hooks | manual | high | The user export is profiles. Actions, Rules, and custom database scripts do not come with it. Auth0 notes that the same libraries are on npm, which means you rewrite the code. | List every hook that still changes a token or a profile. Test it against one copied user. |
| User ids | partial | critical | Supabase user ids are UUID v4. You can pass an id only when you already have one in that form. An Auth0 id such as auth0|abc is not a UUID. | Let Supabase assign the id. Store the Auth0 user id on app_metadata or in your own user table. |
| Duplicate emails | manual | high | Automatic linking attaches identities that share a verified email to one Supabase user. Auth0 can hold separate accounts on that same email. Parqet had a few thousand of those, and about 100 they could not merge with a rule. | Decide the surviving account before createUser. Write down the ones you skip. |
| Metadata and roles | partial | high | user_metadata is for fields the person can change. app_metadata is for fields they should not, such as a role. Both are copied into the access token. The hash export does not include this metadata. You merge it from the profile export. | Keep the token small. Put a long profile in your own table keyed by the Supabase user id. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Database user (identities provider auth0) | auth.users via createUser | transform | Pass password_hash. Set email_confirm when Auth0 already verified the address. |
| Password hash from the support export | auth.users.encrypted_password | transform | Supabase accepts bcrypt and argon2. New password users it creates itself use bcrypt. |
| Auth0 user_id | app_metadata or your user table | manual | Join it to oid on the hash file, per the Supabase guide. Do not pass it as the Supabase id unless it is already a UUID. |
| user_metadata | user_metadata | transform | The person can update this. It also lands in the JWT. |
| app_metadata and roles | app_metadata | manual | Roles the user must not edit go here, still inside the JWT. |
| Social identity | auth.identities after signInWithOAuth | automatic | Match provider|id to the old account. Same OAuth client id avoids a new consent screen. |
| Verified phone or email, no password | Confirmed user, then signInWithOtp | transform | Create the user with email_confirm or phone_confirm set. |
| TOTP factor | No destination | unsupported | You can request the secret. The guide still says the person may enroll again. |
| Organizations | No destination | unsupported | Supabase's guide does not move Auth0 organizations, so rebuild membership yourself. |
| Sessions and refresh tokens | No destination | unsupported | People sign in again. |
Make the move recoverable.
Create the source-of-truth backup
Keep a profile export you can re-read, and keep the decrypted hashes off the app repo.
- Count users per connection in the Auth0 dashboard.
- Run one sample export of 10 users and check the fields.
- Export each connection you are keeping. The dashboard job is per connection. The Management API exports every connection if you omit connection_id.
- Download the file. The location URL lives for 60 seconds. You can request that link again during the 24 hours before the job is deleted.
- Store the JSON outside the repo, with the counts and the job id.
Proof to capture: Per-connection counts, the export file location you archived, and the name of the admin who can rerun the job.
Request the hash export before you need it
Passwords do not move until Auth0 support sends the PGP file.
- Generate an RSA 4096 key with gpg --full-generate-key. Pick RSA and RSA. An ECC key is rejected. The uid email has to match the tenant admin who opens the case, including capitalization.
- Export the public key with gpg --armor --export and the key id from gpg --list-keys. Stay under 35,000 characters. Set the key to expire no sooner than 7 days out.
- Open the case with the tenant name and the public key. Auth0 reviews eligibility and does not give an ETA.
- If they approve, confirm the export in writing. A second tenant admin confirms it too, unless you are the only admin. A CISO, CSO, or VP signs the acknowledgment. A typed name is not enough.
- Download within 3 days. Only the person who opened the case can download, and they still need the tenant admin role. Decrypt with gpg --output hashes.json --decrypt export.pgp. Never send Auth0 the private key.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Create the Supabase users from the two files
The profile export and the hash export are different files. The import joins them.
- Filter database users whose identities include the provider auth0. Supabase's guide says to match that user_id to oid on the hash row. Read the file you received before you trust the column name.
- Call createUser with the email, password_hash, and email_confirm true when Auth0 already verified the mailbox. Use the service role on a machine that is not the browser.
- Put the Auth0 user id in app_metadata or in your own table. Pass id only when you already have a UUID.
- For a miss, try Supabase sign-in, then Auth0, and create the user only after Auth0 accepts the password. That is the fallback in the Supabase guide.
- Social users can wait until signInWithOAuth. Passwordless users are created confirmed, then they get a code.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
The hash file is late or short
Auth0 does not promise a delivery time. Parqet waited about a week for a test file and about a week and a half for the final one, which arrived at 2am on a Saturday.
- Consequence
- You import on their clock. Parqet found 1,600 people missing after more than 72,000 rows were already in, and those people needed a reset email.
- Mitigation
- Open the case first. Diff the hash file against the profile export before you disable Auth0.
Stop if: The hash file is missing database users and you have no reset path for them.
Two Auth0 accounts share an email
Supabase links a verified email to one user. Auth0 does not.
- Consequence
- The second account is dropped, or the wrong history gets attached. Parqet still had about 100 cases to do by hand.
- Mitigation
- Pick the surviving account from your own activity data before createUser.
Stop if: A sample login opens a different app profile than the one that owns the data.
The new client is not in people's hands
A web deploy is fast. A store review is not. Parqet's iOS release sat in App Review and held the toggles.
- Consequence
- You turn off Auth0 while the installed app can only talk to Auth0.
- Mitigation
- Ship the Supabase client, and keep the Auth0 path, before you disable a connection.
Stop if: The build people are running cannot verify a Supabase session.
A mobile session dies after an hour
Loper set autoRefreshToken and also refreshed the token themselves. Supabase treated that as spoofing and revoked it.
- Consequence
- People are logged out when they reopen the app, which looks like a failed migration.
- Mitigation
- Leave one device backgrounded past the access-token lifetime and sign a request after that.
Stop if: A test device is signed out with no password error and no expired-code error to explain it.
Do the work in this order.
- Day 1 · case
Open the hash case and count the tenant
2 to 4 hours active1 day elapsedNone yet. The wait starts when the case is open. waiting- Count users per connection.
- Note which connections are database, social, passwordless, or SAML.
- Generate the PGP key and open the support case.
- List organizations, Actions, and Rules you will rebuild.
Stop / go checkpointCan this tenant export hashes?
Go when: The plan is paid, the case is open, and every connection has an owner.
Stop when: The tenant is on the free plan, or a custom database holds the passwords and nobody has said so.
- Days 1 to 3 · profiles
Export profiles and find duplicate emails
Half a day active1 to 3 days elapsedThe export job waiting- Download a sample of 10, then the full JSON.
- Group database users, social users, and duplicate emails.
- Store the Auth0 id next to your app user id.
Depends on: case
Stop / go checkpointIs the profile snapshot complete?
Go when: Counts match the dashboard, and duplicate emails have a surviving account.
Stop when: A connection is missing or the job expired before you downloaded it.
- When the test hash file arrives · pilot
Import a handful of users
Half a day activeHowever long Auth0 takes, often more than a week elapsedThe support export waiting- Decrypt the test file.
- createUser for one password user, one social user, and one duplicate-email pair.
- Sign in with the known password. Send one passwordless code if you use them.
Depends on: profiles
Stop / go checkpointDoes the join work?
Go when: The known password works and the social user maps to the old app profile.
Stop when: oid does not match user_id, or the test user is asked to verify an address Auth0 already verified.
- After the pilot · clients
Ship the client that can talk to both
A few days activeA few days, longer if a store has to review the build elapsedApp Review, if you have a native app waiting- Verify both JWTs in the API.
- Add a toggle per login method.
- Release the web app and the store build before you flip anything.
Depends on: pilot
Stop / go checkpointCan a production build sign in with Supabase?
Go when: The build people already have accepts a Supabase session and still accepts Auth0.
Stop when: Only a local build can reach Supabase.
- When the final hash file arrives · backfill
Backfill passwords and turn providers over
A day active1 day plus the export wait elapsedThe final hash file waiting- Diff the hash file against the profile export.
- Import the remaining database users.
- Turn social sign-in to Supabase. Leave Auth0 up for the fallback.
Depends on: clients
Stop / go checkpointAre the missing hashes explained?
Go when: Every database user is in Supabase or on the reset list.
Stop when: The file is short and the reset mail is not ready.
Cut over with a way back.
Cutover
New sign-ins go to Supabase, and Auth0 is still there if a password misses.
- Stop new signups on Auth0. Write down the time.
- Export profiles once more and import anyone created after the last file.
- Point each login method at Supabase. Keep the Auth0 password check as the fallback from the Supabase guide: Supabase first, Auth0 if that fails, createUser only after Auth0 accepts the password.
- When refreshes should die, disable that Auth0 social connection. Parqet did this so the old token failed and the next open went through Supabase. Do it only after the new client is out.
- Leave the Auth0 tenant billed. Do not delete it this week.
Proof to capture: The signup freeze time, the diff of the last profile export, and a password login plus one social login on production.
Rollback
Send sign-in back to Auth0 without throwing away the Supabase rows you may need to diff.
- Point the login toggles back at Auth0.
- Re-enable any social connection you turned off.
- Leave the Supabase users in place.
- If someone signed up only on Supabase during the overlap, write that id down before you turn the new signup off.
- Read one Auth0 login and one token refresh before you call the rollback done.
Proof to capture: A production login through Auth0, and a list of accounts that exist only in Supabase.
- The known-password test user cannot sign in on Supabase.
- A social login creates a second app profile.
- The hash file is missing people and the reset mail has not gone out.
- The installed client cannot verify a Supabase session.
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
USR-01Blocking | Counts per connection | Compare the Auth0 export counts with auth.users, split by database, social, and passwordless. | Counts match, or the exception list names every skip. | The count table. | |
PWD-01Blocking | A known password still works | Sign in on Supabase as the test user whose password you set in Auth0. | The session is issued and the app loads the old profile. | The user id and the time of the login. | |
HSH-01Blocking | Every database user is joined or listed | Diff Auth0 user_id values against the hash file. | No silent gaps. Missing rows are on the reset list. | The diff and the reset list. | |
SOC-01Blocking | Provider id maps to the old profile | Sign in with each social provider you still offer. | The app user id is the one from before the move. | The provider id and the app user id. | |
DUP-01Blocking | Shared emails have a decision | Open the duplicate-email list and the Supabase user each one became. | Each pair has a surviving account, and the other is not a second profile. | The decision list. | |
MFA-01Blocking | A TOTP user can enroll | Walk one factor through enroll and a second factor prompt on Supabase. | The test user enrolls on Supabase and passes a second prompt. | The test user. Skip only if the tenant has no MFA, and write that down. | |
EML-01Blocking | Already-verified mailboxes stay verified | Sign in as a user Auth0 marked verified. | Supabase does not send a fresh verification before the session. | The user id and the confirm flag. | |
ORG-01Blocking | Org membership has a home | List Auth0 organizations and where each membership went. | Each org is rebuilt in your tables or named as dropped. | The org list. If you have none, the note says so. | |
APP-01Blocking | The API accepts the new token | Call a signed-in route with a Supabase access token for a migrated user. | The route returns the same app profile as an Auth0 token did. | The response and the user id. |
Remove the scaffolding safely.
Safe after: A week of Supabase sign-ins with no Auth0 fallback hits, and the reset list is either mailed or empty.
- Delete the decrypted hash file from laptops and CI. Keep the PGP private key only while you still need to decrypt that download. The link itself dies after 3 days.
- Revoke the Management API token you used for the export.
- Remove the Auth0 JWT check only after the rollback window.
- Keep the tenant until finance has the last invoice and you are sure you will not need another hash export.
- Set the next review date.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Kevin Grüneberg describes Parqet moving 125,000 registered users from Auth0 to Supabase over a few weeks. The product had Google, Apple, Facebook, and email-password logins, a Nuxt web app, a native iOS app, and Circle single sign-on. They toggled each social provider first, then imported bcrypt hashes that Auth0 sent only after a support request. The final file arrived at 2am on Easter Saturday and was missing 1,600 people after more than 72,000 had already been imported, so those accounts got a generated password and a reset mail. A few thousand duplicate emails had to be merged, about 100 by hand, and an App Review hold delayed the iOS release.
- Open the password-hash support case before the import, and ask for a test file. Parqet waited about a week, then another week and a half, and could not choose the delivery time.
- Match linked accounts and duplicate emails before createUser. They noticed Auth0's link extension late and had to reconnect a slice of people afterward.
- The bcrypt hashes worked in Supabase without a rewrite, but they arrived as a support export, not in the normal user download.
- Disabling an Auth0 social connection made old refreshes fail, so the next open went through Supabase. He still spent the following days on token refresh bugs and duplicate emails.
Sam Eigen writes up Loper's rolling move from Auth0 to Supabase. Loper is a React Native and Expo app with more than 100,000 users, and sign-in was a code by email or phone. The post-startup Auth0 renewal was not a price they could pay, and they had less than a month, over the holidays. With no passwords stored, the first successful sign-in created the Supabase user and updated their own user row. About a month later the first Supabase bill was $50. The rough edge was sessions: autoRefreshToken plus their own refresh looked like spoofing, Supabase revoked the token, and people were logged out until that flag was turned off.
- If the product is passwordless, create the user on the next login and keep Auth0 able to send codes until the people you still care about have signed in once.
- On React Native, leave a session past the access-token lifetime before you call it done. Loper's logout bug showed up when people reopened the app.
- He had less than a month because the Auth0 renewal was the deadline, and the first Supabase bill a month later was $50.
- They kept Twilio and Mandrill, configured inside Supabase. The logout bug was the autoRefreshToken flag on the React Native client.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Supabase: migrate from Auth0Accessed 2026-11-07
- Supabase: identity linkingAccessed 2026-11-07
- Auth0: bulk export user dataAccessed 2026-11-07
- Auth0: export password hashes and MFA secretsAccessed 2026-11-07
- Auth0: export dataAccessed 2026-11-07
- Kevin Grüneberg: migrating 125,000 Parqet users from Auth0 to SupabaseAccessed 2026-11-07
- Sam Eigen: Loper's Auth0 to Supabase migrationAccessed 2026-11-07