Migrar Firebase Auth a Supabase sin forzar reset de contraseña — Cesar Ayala
← Todos los artículos

Migrar Firebase Auth a Supabase sin forzar reset de contraseña

Exporta usuarios con firestoreusers2json.js, copia los cuatro parámetros scrypt de Firebase (base64_signer_key, base64_salt_separator, rounds, mem_cost) desde Authentication → password hash parameters, e importa con import_users.js a auth.users de Supabase. Como GoTrue verifica los hashes scrypt de Firebase con esos parámetros, cada usuario entra con su misma contraseña, sin reset.

La respuesta corta: importas los hashes de Firebase, no los reseteas

Sí: puedes migrar de Firebase Auth a Supabase conservando las contraseñas de tus usuarios, sin un solo correo de reset. Firebase guarda un hash scrypt de cada contraseña; si copias los cuatro parámetros scrypt (base64_signer_key, base64_salt_separator, rounds, mem_cost) desde Authentication → Users → password hash parameters en la Firebase Console e importas los usuarios con import_users.js, Supabase Auth (GoTrue) verifica esos hashes y cada usuario entra con su misma contraseña.

Todo lo que sigue es cómo hacerlo bien, dónde tropieza la gente, y qué hacer con los usuarios que no tienen contraseña (Google, MFA, cuentas deshabilitadas).

Firebase Auth (scrypt)
firestoreusers2json.js
users.json + 4 params
import_users.js
auth.users (Supabase)

Por qué exportar los usuarios “tal cual” rompe todos los logins

El error mental más común es tratar esto como una copia de tabla: saco emails y UIDs de Firebase, los meto en Supabase, listo. El problema es que una cuenta sin su hash de contraseña no es una cuenta, es un email huérfano. Si importas usuarios sin el hash, Supabase no tiene con qué comparar cuando el usuario escribe su contraseña, así que el único camino que te queda es forzar un reset a todos. Y un reset masivo por correo tiene tasas de conversión pésimas: pierdes a los usuarios inactivos, los que ya no leen ese buzón, y los que simplemente asumen que tu app fue hackeada.

El hash es todo el problema. Firebase no guarda contraseñas en texto plano (obvio) ni un hash estándar que puedas re-verificar sin más: usa una variante de scrypt con parámetros propios de tu proyecto. Sin esos parámetros, el hash exportado es ruido. Con ellos, GoTrue reproduce el mismo cálculo que hacía Firebase y valida la contraseña vieja. Por eso la migración de auth se reduce a mover dos cosas juntas: los hashes y los cuatro parámetros que los hacen verificables.

Esto es la contraparte de auth de la migración de datos. Si vienes de mover tus colecciones, el hermano Migrar de Firebase a Supabase (Firestore → Postgres) cubre el lado NoSQL → relacional; aquí nos clavamos solo en los usuarios.

Dónde sacar los cuatro parámetros scrypt en la Firebase Console

Entra a la Firebase Console → Authentication → pestaña Users → menú de tres puntos (⋮) arriba del listado → Password hash parameters. Ahí Firebase te muestra los valores que tienes que copiar tal cual. Son cuatro:

  • base64_signer_key — la llave secreta que Firebase usa para firmar el hash, en base64. Es única de tu proyecto y es la parte más sensible: trátala como una credencial, no la pegues en un repo público.
  • base64_salt_separator — el separador de salt, también en base64. Suele ser un valor corto y fijo, pero cópialo desde tu proyecto, no lo adivines.
  • rounds — el número de rondas del algoritmo (un valor típico es 8).
  • mem_cost — el costo de memoria del scrypt (un valor típico es 14).

Estos cuatro valores, combinados, son lo que le permite a GoTrue recalcular el hash de Firebase byte por byte. Si copias uno mal —un carácter de más en el base64, o rounds y mem_cost invertidos— ningún login funcionará y parecerá que la migración “no sirvió”, cuando en realidad solo tienes un parámetro mal transcrito. Cópialos con cuidado.

base64_signer_keyLlave secreta de firma (base64). Única de tu proyecto. Trátala como credencial.
base64_salt_separatorSeparador de salt (base64). Copia el de tu proyecto, no lo inventes.
roundsRondas del scrypt. Valor típico: 8.
mem_costCosto de memoria del scrypt. Valor típico: 14.

Paso 1: exporta tus usuarios con firestoreusers2json.js

Toda la herramienta vive en el repo comunitario firebase-to-supabase, en el directorio auth/. Antes de correr nada necesitas dos archivos de service account en la raíz del repo: firebase-service.json (credenciales de Firebase) y supabase-service.json (credenciales de Supabase).

Con eso listo, exportas todos los usuarios de Firebase Auth a un JSON local:

node firestoreusers2json.js [<filename.json>] [<batch_size>]

filename.json es el archivo de salida y batch_size cuántos usuarios trae por lote (útil si tienes cientos de miles). Un ejemplo concreto:

node firestoreusers2json.js users.json 100

Esto te deja un users.json con cada usuario, su email, su UID, su metadata y —lo importante— su hash de contraseña scrypt. Ábrelo y verifica que efectivamente trae el campo del hash antes de seguir. Si el archivo salió vacío o sin hashes, casi siempre es que el firebase-service.json no tiene permisos suficientes; arréglalo aquí, no en el siguiente paso.

Paso 2: importa los usuarios a Supabase con import_users.js (con hashes, sin reset)

Ahora importas ese JSON a la tabla auth.users de Supabase con import_users.js, después de haber pegado los cuatro parámetros scrypt en la configuración del script (el repo espera que los coloques donde indica su README; por eso, más abajo, insisto en fijar el repo antes de correr):

node import_users.js <path_to_json_file> [<batch_size>]

Concretamente:

node import_users.js users.json 100

Cada usuario aterriza en auth.users con su hash scrypt de Firebase intacto y los parámetros que GoTrue necesita para verificarlo. Aquí no estás re-hasheando ni pidiendo contraseñas: estás trasplantando el hash existente y diciéndole a Supabase cómo leerlo.

  1. Configura los dos service accountsfirebase-service.json y supabase-service.json en la raíz del repo.
  2. Exporta con firestoreusers2json.jsnode firestoreusers2json.js users.json 100 → genera users.json con hashes.
  3. Pega los 4 parámetros scryptbase64_signer_key, base64_salt_separator, rounds y mem_cost donde indica el README.
  4. Importa con import_users.jsnode import_users.js users.json 100 → puebla auth.users.
  5. Verifica un login realUn usuario existente entra con su contraseña VIEJA. Sin reset.

Paso 3: verifica que un usuario real entre con su contraseña VIEJA

No confíes en que “corrió sin errores”. La prueba real es un login. Toma una cuenta de la que conozcas la contraseña (idealmente una de prueba tuya que ya existía en Firebase) e intenta iniciar sesión contra Supabase con esa misma contraseña vieja:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);

const { data, error } = await supabase.auth.signInWithPassword({
  email: "usuario.existente@ejemplo.com",
  password: "la-contrasena-de-siempre",
});

console.log(error ? `Fallo: ${error.message}` : `Entro: ${data.user?.id}`);

Si imprime Entro: con un UID, terminaste: GoTrue verificó el hash scrypt de Firebase con tus parámetros y el usuario nunca supo que hubo una migración. Si imprime Fallo: Invalid login credentials para todos, tu problema no son las contraseñas: es uno de los cuatro parámetros mal copiado. Vuelve a la Console, cópialos de nuevo, reimporta y reintenta. Prueba varias cuentas, no una sola, antes de mover producción.

La alternativa: middleware de verificación en el primer login

El import directo es lo que quieres la mayoría de las veces. Pero hay un escenario donde conviene la alternativa: cuando no puedes o no quieres confiar el hash scrypt de Firebase a tu Postgres, o cuando estás migrando de forma incremental y no quieres un corte único. El repo comunitario también incluye esta ruta justo para ese caso.

La alternativa es un middleware de verificación en el primer login: importas los usuarios sin contraseña utilizable, y la primera vez que cada usuario intenta entrar, tu backend valida la credencial vieja contra Firebase Auth (con el Firebase Auth SDK), y si es correcta, la re-hashea y la guarda como contraseña nativa de Supabase. A partir del segundo login ya es 100% Supabase.

Import directo (import_users.js)

  • Un solo corte, todos los hashes migran de golpe
  • El usuario entra con su clave vieja sin notar nada
  • Requiere copiar bien los 4 parámetros scrypt
  • Ideal para la mayoría de las migraciones

Middleware de primer login

  • Re-hashea cada credencial la primera vez que el usuario entra
  • No necesitas confiar el hash de Firebase a tu DB
  • Sirve para cortes graduales, sin big-bang
  • Más código y estado que mantener durante la transición

Elige el import directo salvo que tengas una razón concreta de compliance o de rollout gradual. Si estás justo decidiendo tu proveedor de auth de fondo, Better Auth vs Clerk vs Supabase para un SaaS mexicano es la comparación que quieres antes de comprometerte con esta migración.

Preservar UIDs y metadata para no romper tus llaves foráneas

Aquí es donde las migraciones “exitosas” explotan una semana después. Si tu base de datos referencia a los usuarios por el UID de Firebase —comentarios, pedidos, suscripciones, todo colgado de un user_id— y la importación genera UIDs nuevos en Supabase, cada una de esas llaves foráneas queda apuntando a un usuario que ya no existe.

La regla: preserva el UID de Firebase como el id en auth.users. import_users.js está pensado para conservar el identificador del usuario; verifícalo comparando un puñado de UIDs entre tu users.json exportado y las filas resultantes en auth.users:

select id, email, raw_user_meta_data
from auth.users
where email = 'usuario.existente@ejemplo.com';

Confirma que ese id es idéntico al UID que tenía en Firebase. Además, mete la metadata relevante (nombre, plan, created_at original) en raw_user_meta_data para no perder contexto. Si tus tablas de negocio ya viven en Postgres tras seguir el hermano de Firestore → Postgres, este es el pegamento que mantiene sanas tus relaciones.

Casos borde: Google/federado, MFA y cuentas deshabilitadas

No todos tus usuarios tienen contraseña, y esos no se migran igual:

  • Login con Google / proveedores federados. Estos usuarios nunca tuvieron un hash de contraseña: su identidad vive en Google, no en Firebase. No hay hash que copiar. Lo que migras es el vínculo de identidad, y tienes que configurar el mismo proveedor OAuth (Google, etc.) en Supabase Auth. Cuando el usuario vuelve a entrar “con Google”, Supabase lo reconoce por su email/identidad, no por una contraseña.
  • MFA (segundo factor). La inscripción de MFA de Firebase no cruza automáticamente. Planea que estos usuarios re-inscriban su segundo factor en Supabase en su primer login. No es un reset de contraseña, pero sí un paso de re-enrolamiento que debes comunicar.
  • Cuentas deshabilitadas. Si tenías usuarios disabled en Firebase, decide explícitamente si los importas deshabilitados o los omites. Importar cuentas deshabilitadas “por si acaso” y luego olvidarte de ellas es cómo terminas con usuarios baneados que de pronto pueden entrar.

Un último recordatorio que no es de usuarios pero es del mismo corte: las Firebase Security Rules no se importan. Se reescriben a mano como políticas de Row Level Security (RLS) en Postgres, basadas en auth.uid(). Trátalo como una tarea propia y prueba cada política; ve la guía oficial de migración de Firebase Auth de Supabase para el detalle.

Fija los scripts y las rutas de la Console antes de correr — el repo cambia

El repo firebase-to-supabase es comunitario y evoluciona: nombres de scripts, dónde exactamente pegas los parámetros scrypt y el flujo de configuración pueden cambiar entre versiones. Antes de correr esto en producción, fija (pin) la versión del repo que vas a usar y confirma dos cosas contra la fuente viva: (1) los nombres y firmas de firestoreusers2json.js e import_users.js en el repo, y (2) que la ruta Authentication → Users → password hash parameters en la Firebase Console sigue mostrando los cuatro valores tal como aquí, según la documentación de migración de Supabase. Verificar cinco minutos contra la fuente te ahorra una migración a medias.

Y si esta migración es parte de un cambio más grande de stack, calcula el costo real de lo que estás construyendo con cuánto cuesta construir un MVP de SaaS antes de comprometer el calendario. La migración de auth es barata en horas; lo caro es descubrir a mitad del camino que faltaba un parámetro.