Migrating auth & passwords
Your users come across with their accounts intact — and, in most cases, without having to reset their passwords. Here's exactly how that works, which source hashes are supported, how to map accounts that live in your own table, and where the edges are.
Passwords: verify-then-upgrade
ichibase hashes passwords with Argon2id, but other providers don't: Supabase and most apps use bcrypt; Firebase uses a modified scrypt. Rather than force a reset, the import stores each user's originalhash as-is, tagged with its scheme. On a user's first login, ichibase verifies the password against the original scheme and, if it matches, transparently re-hashes it to Argon2id and saves that. Every active user converges to the native format over time — invisibly. The per-login overhead is a one-time hash check.
Supported source hashes
| Scheme | Looks like | Carries over? |
|---|---|---|
| bcrypt | $2a$ / $2b$ / $2y$ | Yes — verified, then upgraded on first login. |
| Argon2id | $argon2id$ | Yes — already native, no upgrade needed. |
| Firebase scrypt | Firebase Auth export + your hash params | Yes — verified against Firebase’s scrypt, then upgraded. |
| plain / none | plaintext, or no usable hash | No — user resets via “forgot password”. Account + data still migrate. |
Bring your own users (any source)
The built-in extractors read Supabase auth.users and Firebase Auth automatically. If your accounts instead live in an ordinary table or collection — a users table in a generic Postgres, a users collection in Mongo, or a Firestore collection — map them into ichibase Auth with the --auth-* flags. This works for every source kind and is the only auth path for generic Postgres and Mongo.
| Flag | Purpose |
|---|---|
--auth-table / --auth-collection | The source table/collection holding accounts. |
--auth-email-field | Field with the login email (required). |
--auth-pass-field | Field with the password hash (omit for OAuth-only users). |
--auth-id-field | Field to reuse as the user id (kept if it’s a UUID; else a new UUID is minted and the original saved in metadata). |
--auth-verified-field | Boolean/timestamp field marking the email verified. |
--auth-created-field | Field with the account creation time. |
--auth-hash-scheme | How to tag the hash: bcrypt, argon2, fbscrypt, plain, or none. |
Example — a Postgres app whose accounts are in a bcrypt-hashed `members` table:
ichibase migrate postgres \
--conn "postgresql://user:pw@host:5432/app" \
--auth-table members \
--auth-email-field email \
--auth-pass-field password_hash \
--auth-id-field id \
--auth-verified-field email_verified \
--auth-hash-scheme bcrypt \
--out ./bundlebcrypt/argon2 hashes are carried over verbatim (login lazily upgrades them). fbscrypt needs Firebase hash params supplied alongside. plain/none means no usable hash is imported and those users reset their password on first login.User IDs are preserved
Imported users keep their original UUIDs. This matters: if your data tables reference auth.users(id) (the standard ownership pattern), those foreign keys stay valid because the user rows land with the same IDs your data points at. The importer loads auth and re-adds foreign keys so everything lines up.
Firebase UIDs aren't UUIDs, so each is mapped to a deterministic UUID (UUIDv5 of the Firebase UID). Because it's deterministic, document fields that referenced a user (an ownerUid, authorId, …) are rewritten to the sameUUID during the Firestore import, so ownership links survive the move. The original UID is kept in the user's metadata.
Email verification & OAuth
A user's verified state carries over (Supabase's email_confirmed_at→ verified; Firebase's emailVerified→ verified), so confirmed users aren't asked to re-verify. Linked OAuth identities (Google, Apple, …) are imported as well, so “Sign in with Google” keeps resolving to the same account — just configure the matching OAuth provider in your project so the federated login resolves.
Sessions
Active sessions / refresh tokens are not migrated — your users simply log in again (with their existing password, thanks to the verify-then-upgrade flow above). This is deliberate: session tokens are provider-specific and short-lived.
