Unbase

Auth Migration

Move users to Better Auth — without making anyone reset their password.

This is the hard part of leaving Supabase, and Unbase's headline feature.

The problem

Supabase Auth (GoTrue) stores credential passwords as bcrypt hashes. Better Auth verifies with scrypt by default. Naively migrating users would leave everyone unable to log in — forcing a password reset for your entire user base.

How Unbase solves it

extract pulls auth.users and auth.identities into a normalized auth model, keeping each user's bcrypt hash verbatim. auth then writes Better Auth rows:

  • user — id, email, emailVerified (from email_confirmed_at), name and image from user metadata.
  • account (credential) — providerId: "credential" carrying the original bcrypt password.
  • account (OAuth) — one per linked identity (google, github, ...), keyed by provider + provider account id.

Account ids are deterministic, so re-running auth is idempotent.

The one config you must add

Tell Better Auth to verify bcrypt hashes:

auth.ts
import bcrypt from "bcryptjs";

export const auth = betterAuth({
  // ...
  emailAndPassword: {
    enabled: true,
    password: {
      hash: (password) => bcrypt.hash(password, 10),
      verify: ({ hash, password }) =>
        /^\$2[aby]\$/.test(hash)
          ? bcrypt.compare(password, hash)
          : Promise.resolve(false),
    },
  },
});

With this, migrated users log in with their existing passwords, and new sign-ups are hashed with bcrypt too — one uniform scheme.

Users with no email are skipped (Better Auth requires one) and reported in the auth output. Sessions are not migrated — users simply log in again.

Other targets

npx unbase auth --to authjs    # Auth.js (NextAuth) instead of Better Auth

The auth mapping is pluggable. Auth.js is supported via --to authjs (writes the standard @auth/drizzle-adapter user/account tables). Note: Auth.js has no built-in credential password store, so bcrypt passwords are not carried for the Auth.js target — OAuth identities migrate, and credential users pair with a Credentials provider or a one-time reset. For password preservation, use the default Better Auth target.

On this page