Firebase signInWithRedirect on localhost: three setups that work

Updated 26 Oct 2026 · Pewterkit Guides

On localhost, signInWithRedirect usually fails for two reasons: localhost is not an authorized domain (new projects no longer include it), or your dev origin differs from your authDomain (<project>.firebaseapp.com), so the browser partitions the storage the redirect needs. Authorize the exact host you use, then pick one of three dev setups below.

Why redirect sign-in breaks during local development

Your Firebase config points authDomain at <project>.firebaseapp.com. During a redirect, the SDK sends the browser to https://<authDomain>/__/auth/handler, and after Google returns the user, it reads the outcome through an iframe loaded from https://<authDomain>/__/auth/iframe. On http://localhost:5173, that iframe is third-party content, and browsers with storage partitioning (Chrome 115+, Firefox 103+ by default) give it a separate storage bucket. Firebase's guide says the cross-origin iframe "doesn't work with browsers that block third-party storage access," and for any host other than Firebase Hosting you need one of its options. Your dev server counts as "a service other than Firebase."

There is a second, simpler blocker. Firebase's docs state (checked Oct 2026): "In projects created after April 28, 2025, Firebase Authentication no longer includes localhost as an authorized domain by default." Older projects still have it.

That explains the two common patterns:

  • Works in production, fails locally: production runs on Firebase Hosting or on a domain already set up as authDomain, while localhost is cross-site to firebaseapp.com, or not authorized.
  • Works locally, fails in production: usually the production domain is missing from authorized domains, or a custom authDomain has no matching redirect URI. Those have their own pages; this page covers the local side.

Step 1: authorize the host you actually open

As of Oct 2026, Firebase's docs give the path as Security > Authentication > Settings > Authorized domains; older guides and older console layouts show it under Build > Authentication > Settings. Click Add domain and add localhost (Google recommends not leaving localhost in production projects, so consider a separate dev project). If you open the app at 127.0.0.1 instead, add that. Details and the exact error text: auth/unauthorized-domain on localhost.

Step 2: pick a dev setup

Setup A: mirror production with a dev-server proxy (keeps real redirect)

This applies Firebase's Option 3 (reverse proxy) to your dev server, so the handler and iframe are served from localhost itself. Two details are easy to miss:

  • In the current SDK source, the handler and iframe URLs are always https://<authDomain>/__/auth/..., so your dev server must serve HTTPS when authDomain is localhost:<port>.
  • The proxy must be transparent. Firebase: "this cannot be done via a 302 Redirect."

Vite example:

npm i -D @vitejs/plugin-basic-ssl
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import basicSsl from "@vitejs/plugin-basic-ssl";

export default defineConfig({
  plugins: [react(), basicSsl()], // self-signed cert for https://localhost
  server: {
    port: 5173,
    strictPort: true, // the redirect URI below includes this port
    proxy: {
      "/__/auth": {
        target: "https://YOUR_PROJECT.firebaseapp.com",
        changeOrigin: true,
      },
    },
  },
});

Point authDomain at the dev server only in development, using a mode-specific env file:

# .env.development.local  (loaded only in development mode; keep *.local files out of git)
VITE_FIREBASE_AUTH_DOMAIN=localhost:5173
# .env.production
VITE_FIREBASE_AUTH_DOMAIN=your-production-auth-domain
// firebase.js
const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
};

Then add https://localhost:5173/__/auth/handler to Authorized redirect URIs on the OAuth Web client your Firebase project uses (Google Cloud console > Google Auth Platform > Clients; older guides: APIs & Services > Credentials; as of Oct 2026). Google's validation rules exempt localhost from the HTTPS-only and no-IP rules, but the URI must still match exactly, port included. If you get redirect_uri_mismatch, see redirect_uri_mismatch on localhost.

Start npm run dev, open https://localhost:5173, and accept the self-signed certificate warning once. Other dev servers can work the same way if they support HTTPS and a transparent path proxy. Wondering whether you also need /__/firebase/init.json? See do I need init.json.

Firebase documents this proxy for app servers, not for dev servers. Test this on a preview deployment before relying on it, and confirm the full sign-in round trip works locally.

Setup B: use a popup in development only

The fastest route. Firebase lists switching to signInWithPopup as a valid fix, and it needs no proxy or certificate:

import { signInWithPopup, signInWithRedirect, GoogleAuthProvider } from "firebase/auth";

const provider = new GoogleAuthProvider();

export function signInWithGoogle(auth) {
  return import.meta.env.DEV
    ? signInWithPopup(auth, provider)     // local dev
    : signInWithRedirect(auth, provider); // production
}

Trade-off: you are not exercising the redirect path locally, so test it on a preview deployment before release. Keep getRedirectResult in your startup code; it returns null when no redirect is pending.

Setup C: the Firebase Auth Emulator

The emulator "serves a local version of the corresponding sign-in page" for apps that call signInWithPopup or signInWithRedirect, and "creates mock third-party user accounts and credentials as needed." No real Google account is needed, and the sign-in does not go through your Google OAuth client.

firebase emulators:start --only auth
import { getAuth, connectAuthEmulator } from "firebase/auth";

const auth = getAuth(app);
if (import.meta.env.DEV) {
  // Call right after creating auth, before any other auth call
  connectAuthEmulator(auth, "http://127.0.0.1:9099");
}

Firebase's reference says connectAuthEmulator "must be called synchronously immediately following the first call to initializeAuth()" and should not be used "with production credentials." For emulator-only work, Firebase also supports demo projects (IDs start with demo-), which talk only to emulators. One caveat: firebase-js-sdk issue #7607 reports getRedirectResult always returning null with the emulator in one setup. If you hit that, use Setup B against the emulator, or Setup A against the real project.

How to confirm it worked

  1. DevTools > Network: after clicking sign-in, the first navigation goes to https://localhost:5173/__/auth/handler (Setup A), a popup window opens for Google sign-in (Setup B), or the first navigation goes to http://127.0.0.1:9099/emulator/auth/handler (Setup C).
  2. After you return, getRedirectResult(auth) resolves once with a result (operationType: "signIn"), and onAuthStateChanged fires with a user.
  3. Reload the page: the user stays signed in and getRedirectResult now returns null, which is expected.
  • Redirect works on localhost now but fails on your deployed domain: work through signInWithRedirect not working for production.
  • React StrictMode makes the result vanish in development: the second getRedirectResult call returns null. The React pattern is in signInWithRedirect in React.

FAQ

Why does Firebase redirect sign-in work in production but not on localhost?

Production is often on Firebase Hosting or on a domain already set as authDomain, so the helper iframe is first-party. On localhost, the firebaseapp.com iframe is third-party and its storage is partitioned. In projects created after April 28, 2025, localhost also has to be added to authorized domains by hand.

Can I set authDomain to localhost?

Only if your dev server serves /__/auth/* for that host over HTTPS, because the current SDK builds the handler URL as https://<authDomain>/__/auth/handler. Proxy the path to <project>.firebaseapp.com (Setup A) and add the matching redirect URI to your OAuth client.

Is localhost an authorized domain by default?

For projects created before April 28, 2025, yes. For projects created after that date, Firebase no longer includes it, so add it under Authentication > Settings > Authorized domains.

Does the Auth Emulator need my Google OAuth client?

No. The emulator serves its own sign-in page and creates mock accounts, so the redirect URIs on your Google OAuth client are not involved.

Sources

  1. Best practices for using signInWithRedirect on browsers that block third-party storage access
  2. Authenticate with Firebase Using Email Link in JavaScript (localhost authorized-domain note)
  3. Sign in users with Google (Identity Platform, localhost default note)
  4. Connect your app to the Authentication Emulator
  5. firebase/auth module reference (connectAuthEmulator, getRedirectResult)
  6. Using OAuth 2.0 for Web Server Applications (redirect URI validation rules)
  7. Get your Google API client ID (Clients page)
  8. Vite server options (server.https, server.proxy, strictPort)
  9. Vite env variables and modes
  10. @vitejs/plugin-basic-ssl README
  11. Install, configure and integrate Local Emulator Suite (--only flag)
  12. Storage Partitioning (Chrome for Developers)
  13. State Partitioning (MDN)
  14. firebase-js-sdk source: core/util/handler.ts
  15. firebase-js-sdk issue #7607: getRedirectResult always returns null on emulator
  16. firebase-js-sdk issue #7716: onAuthStateChanged user returns null when on localhost

How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked 26 Oct 2026. Spotted something out of date? Tell us.

Related guides