loginos

What is a good Firebase Auth alternative?

loginos teamUpdated 7 min read

Short answer

Good Firebase Auth alternatives are Supabase Auth for Postgres apps with row-level security, Clerk or Auth0 for hosted sign-in with enterprise features, Amazon Cognito on AWS, Appwrite for an open-source backend, and Keycloak or Zitadel to self-host. To move, export users with their scrypt password hashes or let people link accounts, and keep each Firebase uid mapped to the new id, which loginos supports through external_id.

Firebase Authentication signs people in to web and mobile apps with very little code, and for many apps it stays a good choice. Teams start looking elsewhere when they need enterprise features, want to depend less on Google Cloud, run on many domains, or outgrow custom claims. Below: what Firebase Auth does well, why teams move, the main alternatives, and how to migrate without losing anyone.

What is Firebase Auth good at?

Firebase Authentication is Google’s hosted sign-in service, and it is strongest when your app already lives in Firebase:

  • SDKs everywhere: Apple platforms, Android, the web, Flutter, Unity and C++, plus FirebaseUI, an open-source drop-in sign-in UI.
  • Many sign-in methods: email and password, email links, phone numbers with SMS codes, anonymous accounts, and providers such as Google, Apple, Facebook, GitHub, Microsoft, Twitter and Yahoo.
  • Guest accounts that convert: an anonymous user can add an email or provider later with linkWithCredential and keep their data.
  • Security Rules: Firestore, Realtime Database and Cloud Storage rules read the signed-in user as request.auth, so many apps need no server for access control.

If those features cover you, moving away buys little.

Why do teams move away from Firebase Auth?

Advanced features need Identity Platform

SAML and OIDC sign-in, multi-factor authentication, blocking functions and multi-tenancy aren’t part of standard Firebase Authentication. You get them by upgrading to Firebase Authentication with Identity Platform, a Google Cloud product. The upgrade needs no code migration, but it ties sign-in more closely to Google Cloud, and SAML there works on the web only.

Password hashes use Firebase’s own scrypt

firebase auth:export writes every account to JSON or CSV, with password hashes and salts if your role is Editor or Owner. The hashes use what Firebase calls its internally modified version of scrypt, so a new provider must support that variant or check passwords against Firebase some other way. You also need the project’s hash parameters, base64_signer_key, base64_salt_separator, rounds and mem_cost, from the console.

Every redirect domain must be authorized

Firebase only redirects to domains on the project’s Authorized domains list, added one full domain name at a time. Because browsers block third-party storage, redirect sign-in also needs authDomain on the same site as your app: a custom domain on Firebase Hosting, a reverse proxy for /__/auth/, or popups instead. Chrome 115 and later have required this since 24 June 2024. For a product on many domains, or on customers’ own domains, that is setup work for every new domain.

Custom claims are capped at 1,000 bytes

Custom claims are extra fields in the ID token and the usual place for roles. The payload must not exceed 1,000 bytes, and new claims reach the client only with a new ID token: after the next sign-in, when the old token expires, or when the app calls getIdToken(true). Apps with many roles or per-team permissions often keep them in Firestore instead.

What are the alternatives?

AlternativeRuns whereKeeping Firebase passwords
Supabase AuthHosted, or self-hostedOfficial import tools that ask for your hash parameters
ClerkHostedImports hashes with the scrypt_firebase hasher
Auth0HostedAutomatic migration checks each password once, at sign-in
Amazon CognitoHosted on AWSA migrate user Lambda trigger checks each password at sign-in
AppwriteHosted, or self-hostedCreates users from “Scrypt modified” hashes
loginosHostedNot needed: people link their accounts

Supabase Auth keeps users in your project’s Postgres database and works with Row Level Security, which filters rows per user inside Postgres. It offers passwords, magic links, one-time codes, phone sign-in, social logins and, on Pro plans and above, SAML 2.0. Supabase is open source and can be self-hosted. Its firestoreusers2json and import_users tools move Firebase accounts into auth.users.

Clerk is hosted, with prebuilt components and organizations. Its Firebase guide imports users with password_hasher: "scrypt_firebase" and stores each Firebase uid as external_id, so passwords keep working and old data stays reachable.

Auth0, part of Okta, brings Organizations and enterprise connections. Its bulk import documents standard scrypt but not Firebase’s variant. Automatic migration avoids that: the first time someone signs in, your script checks the password against the old system and Auth0 creates the user.

Amazon Cognito suits apps already on AWS. CSV import can carry bcrypt, standard scrypt, Argon2id or PBKDF2 hashes on eligible user pools, but not Firebase’s modified scrypt. The migrate user Lambda trigger fills that gap by checking each password against Firebase at the first sign-in.

Appwrite is an open-source backend under the BSD-3-Clause license that you can self-host with Docker. Its migration wizard reads Firebase users, and its Users API creates accounts from Firebase’s “Scrypt modified” hashes. OAuth users aren’t migrated: they sign in again with their provider.

Self-hosted identity providers like Keycloak or Zitadel give you full control and SAML, at the cost of running a server. See simpler alternatives to Keycloak.

How do you migrate users off Firebase Auth?

  1. Export the accounts. Run firebase auth:export users.json --format=json --project <id> with an Editor or Owner role, and copy the password hash parameters from Authentication > Users in the console. Treat both as secrets. Accounts imported earlier with another algorithm export with empty hash fields until they sign in once.
  2. Keep the user id mapping. The export calls the Firebase uid localId, and your Firestore documents, Storage paths and Security Rules probably use it. Keep it as the new user id if the provider allows that, or store it next to the new id. Never match old and new accounts by email unless the new provider verified the address.
  3. Choose a password strategy: import the hashes, check each password at the first sign-in, ask everyone to reset, or link accounts so passwords never move.
  4. Plan for social accounts. Accounts from Google, Apple or GitHub have no password. The export lists each provider’s user id as rawId under providerUserInfo, which helps you match them at the new provider.
  5. Keep Firebase services working during the move. If Firestore or Storage stay, your server can turn a sign-in at the new provider into a Firebase session. It creates a custom token for the mapped uid with getAuth().createCustomToken(uid), and the client signs in with signInWithCustomToken(). Security Rules keep seeing the same request.auth.uid.

Where loginos fits

loginos is a hosted OpenID Connect provider for products on many domains. For a Firebase migration, the useful part is account linking: password hashes never leave Firebase.

Each person connects their Firebase-backed account to loginos once, from your settings page, while still signed in the old way. Your server sends their Firebase uid as external_id in a pushed authorization request (PAR, RFC 9126), so the browser never holds it. The link is made when your server redeems the code, and every loginos token after that carries the uid.

your-app.example/settings

J

Jane Doe

id u_1842

loginosLink loginos

loginos.io

Your app account

jane@example.com

Your loginos account

Jane Doe @jane

What your server reads from /oauth/token

{  "sub": "9b1f6c2e-…",  "preferred_username": "jane",  "roles": [    "member"  ]}
1/5Jane already has an account on your site. In its settings she clicks “Link loginos”.
Express
import crypto from "node:crypto";
import { getAuth } from "firebase-admin/auth";

const b64url = (b) => b.toString("base64url");
const basic = "Basic " + Buffer.from(`${process.env.LOGINOS_APP_KEY}:${process.env.LOGINOS_APP_SECRET}`).toString("base64");

// "Link loginos" on your settings page, for someone still signed in with Firebase.
app.get("/settings/loginos/link", async (req, res) => {
  const me = await getAuth().verifySessionCookie(req.cookies.session ?? "", true).catch(() => null);
  if (!me) return res.redirect("/login");

  const state = b64url(crypto.randomBytes(16));
  const verifier = b64url(crypto.randomBytes(32));
  const r = await fetch("https://loginos.io/oauth/par", {
    method: "POST",
    headers: { authorization: basic },
    body: new URLSearchParams({
      response_type: "code",
      redirect_uri: `https://${req.get("host")}/api/auth/login/single/callback`,
      scope: "openid profile email",
      state,
      code_challenge: b64url(crypto.createHash("sha256").update(verifier).digest()),
      code_challenge_method: "S256",
      external_id: me.uid, // the Firebase uid your data already uses
      external_label: me.email ?? me.uid,
    }),
  });
  if (!r.ok) return res.status(502).send("Couldn't start the link.");
  const { authorize_url } = await r.json();
  res.cookie("lo_link", `${state}.${verifier}`, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 1_800_000 });
  res.redirect(authorize_url);
});

The callback is your normal sign-in callback: check state, redeem the code with the verifier, and confirm that user.external_id equals the uid of the person who started the link. From then on, “Sign in with loginos” returns that uid, and the custom-token step above keeps your Firestore rules working. People who join through loginos first get your id with PUT /api/v1/users/{sub}/external-id. The linking guide has the whole flow, and migrating without a new sign-up compares the approaches.

loginos fits when you want to keep Firebase uids and skip the password migration, when your product runs on many domains that share one user pool, and when you want passkeys on every domain plus roles and permissions in every token. Domains can be added in the console or from your server, and loginos has no paid plans today.

loginos doesn’t fit if you depend on sign-in methods it lacks: Google, Apple or other social logins, phone codes, anonymous accounts or email links. It also has no SAML, no multi-factor codes, no tenants inside a platform, no self-hosting and no login page on your own domain. loginos doesn’t verify email addresses yet, so never link accounts by matching email.

Frequently asked questions

Can I export password hashes from Firebase Auth?

Yes. firebase auth:export writes all accounts to JSON or CSV, with password hashes and salts when you have the Editor or Owner role. The hashes use Firebase’s modified scrypt, so you also need the project’s hash parameters from the console.

Will users have to reset their passwords?

Not necessarily. Clerk and Appwrite can import Firebase’s scrypt hashes. Auth0 and Amazon Cognito can check each password against Firebase at the first sign-in, and account linking avoids moving passwords at all. A forced reset is the fallback, and it adds a step for every user.

Can I keep Firebase Auth and move my data first?

Yes. Supabase, for example, accepts Firebase ID tokens as a third-party auth provider, so you can query Supabase with Row Level Security while people still sign in with Firebase. Each user needs a role: 'authenticated' claim, which you add with a blocking function or an onCreate function.

One account for every domain you run

loginos is an OpenID Connect provider with passkeys, a user pool shared by all of your domains, roles in every token, and account linking for the users you already have. Add your platform and its domains in a few minutes.

Keep reading

All answers