🚀 Executive Summary
TL;DR: This guide outlines a zero-downtime, seamless strategy for migrating users from Firebase Auth to Supabase Auth, specifically addressing the challenge of incompatible password hashes. It details a ‘lazy migration’ approach where user passwords are securely updated in Supabase only upon their first successful login post-migration, leveraging the Firebase Auth REST API for temporary password verification.
🎯 Key Takeaways
- Firebase Auth uses a modified `SCRYPT` hash for passwords, which is incompatible with Supabase’s default `bcrypt` hashing, preventing direct hash import.
- A ‘lazy migration’ strategy involves exporting Firebase user metadata (including password hashes and salts), creating users in Supabase without passwords, and then verifying user-provided passwords against the Firebase Auth REST API during their first login to set the password in Supabase.
- The Supabase `service_role_key` grants ‘god-mode’ permissions and must never be exposed client-side; all user import and password update logic must be handled on a secure backend.
- Proper Firebase IAM permissions (e.g., ‘Firebase Authentication Admin’ role) are essential for exporting user data, and rate limiting for large user bases requires implementing sleep/retry logic in migration scripts.
Moving from Firebase Auth to Supabase Auth
Hey there, Darian Vance here. As a Senior DevOps Engineer at TechResolve, I’m always looking for ways to streamline our stack, reduce vendor lock-in, and get more control over our data. For a long time, Firebase Auth was our go-to. It’s solid, no doubt. But the “black box” nature of it and the appeal of having our auth data right next to our application data in Postgres led my team to explore Supabase. The migration looked daunting at first, especially with user passwords. I spent a fair bit of time perfecting a zero-downtime, seamless migration strategy for our users, and I figured I could save you the same headache. Let’s walk through it.
Prerequisites
Before we get our hands dirty, make sure you have the following ready to go:
- A Firebase project with Authentication enabled and, of course, some users to migrate.
- A Google Cloud service account key (a JSON file) for your Firebase project with appropriate permissions to read user data.
- A new Supabase project. You’ll need the Project URL and the
service_role_keyfrom the API settings. - Python 3 installed on your machine. I’ll be using Python for the scripting, as it’s great for these kinds of data-handling tasks.
- The necessary Python libraries. You’ll need to install `firebase-admin` and `supabase` for your project, typically by running a pip command to install them.
The Step-by-Step Migration Guide
Alright, let’s dive in. I’ll skip the standard virtual environment setup since you likely have your own workflow for that. We’ll jump straight to the logic.
Step 1: Export Users from Firebase
First, we need to get our user data out of Firebase. We can’t just get plaintext passwords (for obvious security reasons), but we can get all the other metadata, including the password hashes. We’ll use the `firebase-admin` SDK for this.
Create a Python script, let’s call it `export_firebase_users.py`. The goal here is to connect to your Firebase project, iterate through all your users, and dump their essential data into a JSON file.
import firebase_admin
from firebase_admin import credentials, auth
import json
# Initialize the Firebase Admin SDK
# Make sure 'firebase-service-account.json' is in the same directory
cred = credentials.Certificate('firebase-service-account.json')
firebase_admin.initialize_app(cred)
all_users = []
page = auth.list_users()
print("Starting user export from Firebase...")
while page:
for user in page.users:
user_data = {
'uid': user.uid,
'email': user.email,
'email_verified': user.email_verified,
'display_name': user.display_name,
'phone_number': user.phone_number,
'photo_url': user.photo_url,
'disabled': user.disabled,
'created_at': user.user_metadata.creation_timestamp,
# IMPORTANT: We grab the password hash and salt for later
'password_hash': user.password_hash,
'password_salt': user.password_salt
}
all_users.append(user_data)
# Get the next page of users
page = page.get_next_page()
# Write the data to a file
with open('firebase_users.json', 'w') as f:
json.dump(all_users, f, indent=4)
print(f"Successfully exported {len(all_users)} users to firebase_users.json")
After running this script, you’ll have a `firebase_users.json` file. This is our source of truth for the migration.
Pro Tip: Firebase’s password hash is a modified `SCRYPT` hash. This is the single most important detail in this entire process. You cannot directly import this hash into Supabase, which uses `bcrypt` by default. We’ll address how to handle this in Step 3.
Step 2: Import User Records into Supabase
Now, let’s create the user records in Supabase. We will create them *without* a password for now. This step simply reserves their email and user ID in your new system.
Create another script, `import_supabase_users.py`. This script will read our JSON file and use the Supabase admin client to create each user.
import os
import json
from supabase import create_client, Client
# Load your Supabase credentials securely
# I recommend using a config.env file or environment variables
SUPABASE_URL = "YOUR_SUPABASE_URL"
SUPABASE_SERVICE_KEY = "YOUR_SUPABASE_SERVICE_KEY"
supabase: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
print("Starting user import into Supabase...")
with open('firebase_users.json', 'r') as f:
users_to_import = json.load(f)
successful_imports = 0
failed_imports = 0
for user in users_to_import:
try:
# We create the user with their email but NO password
# The user will be in a state waiting for their first login to set the password
new_user = supabase.auth.admin.create_user({
"email": user['email'],
"email_confirm": True, # Assume verified if they were in Firebase
# You can map other metadata here if you like
"user_metadata": {
'display_name': user.get('display_name'),
'firebase_uid': user.get('uid')
}
})
successful_imports += 1
print(f"Successfully created user: {user['email']}")
except Exception as e:
failed_imports += 1
print(f"Failed to create user {user['email']}: {e}")
print(f"\nImport complete. Success: {successful_imports}, Failed: {failed_imports}")
At this point, all your user records exist in Supabase, but none of them can log in with a password. This is by design. The magic happens next.
Step 3: The “Lazy Migration” Login Flow
This is where we ensure a seamless experience for our users. Instead of forcing a password reset on everyone, we’ll migrate them one-by-one as they log in for the first time post-migration. This requires a custom API endpoint on your backend that acts as a proxy to your login logic.
The logic is as follows:
- User tries to log in to your app with their email and password.
- Your backend first attempts to sign them in using the standard Supabase `sign_in_with_password` function.
- If it succeeds, the user was already migrated. Perfect. Return the session token.
- If it fails with an “Invalid login credentials” error, this is likely an un-migrated user. Now, your backend needs to verify their password against the old Firebase hash. The safest way to do this is by calling the Firebase Auth REST API’s `signInWithPassword` endpoint.
- If the Firebase API call is successful, it means the password was correct! Now you have a critical window:
- Take the correct password the user just provided.
- Use the Supabase Admin client (`update_user_by_id`) to set this password for the user in your Supabase database. Supabase will automatically hash it with `bcrypt`.
- Sign the user into Supabase and return the new session token.
- The user is now logged in and their password has been seamlessly migrated to Supabase. The next time they log in, Step 3 will succeed immediately.
Pro Tip: Do not try to implement Firebase’s `SCRYPT` verification yourself. It’s complex and undocumented. Using the Firebase REST API as a temporary verification oracle is far more robust and secure. You’ll eventually decommission this endpoint once most of your active users have migrated.
Common Pitfalls
I’ve done this a few times, and here is where I usually mess up or see others get stuck:
- Firebase IAM Permissions: The service account you use for the export needs the “Firebase Authentication Admin” role, or at least `firebaseauth.users.list`. It’s easy to create a key with insufficient permissions and get cryptic errors.
- Rate Limiting: If you have tens of thousands of users, both Firebase’s export and Supabase’s import APIs might rate-limit you. You’ll need to add some sleep/retry logic to your scripts to handle this gracefully. Process users in batches of a few hundred at a time.
- Leaking Service Keys: Never, ever, put your Supabase
service_role_keyin your client-side code. It has god-mode permissions. All the user import and password update logic must happen on a secure backend you control. - Ignoring Social Logins: This guide focuses on email/password users. If you use Google, GitHub, etc., you’ll need to handle that separately. The good news is it’s much easier, as you’re just migrating the user record, not a password. The user just needs to log in again with the social provider.
Conclusion
Moving auth providers feels like open-heart surgery for your application, but it doesn’t have to be. This “lazy migration” approach prioritizes the user experience above all else. There are no mass emails, no confusing password reset flows—it just works. By using Supabase, you gain the immense power of having your auth system built directly on Postgres, opening up possibilities for complex policies and database triggers that were simply impossible with a separate service. It’s a bit of work upfront, but the long-term benefits in control, flexibility, and developer experience are absolutely worth it.
🤖 Frequently Asked Questions
âť“ How do I migrate user passwords from Firebase Auth to Supabase Auth?
To migrate passwords, you must implement a ‘lazy migration’ flow. Export user metadata and `SCRYPT` hashes from Firebase. Create users in Supabase without passwords. On a user’s first login attempt post-migration, if Supabase login fails, verify their password against the Firebase Auth REST API. If successful, use the provided password to update the user’s record in Supabase via the admin client, which will `bcrypt` hash it.
âť“ How does Supabase Auth compare to Firebase Auth for migration?
Firebase Auth is a ‘black box’ service using `SCRYPT` hashes, which complicates direct password migration to Supabase’s `bcrypt`. Supabase Auth, built on Postgres, offers greater control and flexibility over user data and policies, but requires a custom ‘lazy migration’ strategy for passwords, temporarily relying on the Firebase Auth REST API for verification during the transition.
âť“ What are common implementation pitfalls when migrating from Firebase Auth to Supabase Auth?
Common pitfalls include insufficient Firebase IAM permissions for user export (e.g., missing `firebaseauth.users.list`), encountering rate limiting with large user bases (requiring batch processing and retry logic), exposing the powerful Supabase `service_role_key` client-side, and not separately addressing social login migrations which are simpler as they don’t involve password hashes.
Leave a Reply