Horizon

Test Firebase Authentication locally with authorized domains

Test Firebase Authentication sign-in on localhost with a Horizon tunnel, and add the tunnel host to the authorized domains once.

Sign in with Firebase Authentication on your laptop, from a public HTTPS host that Firebase accepts.

Firebase only completes sign-in from domains on your project's authorized domains list. Firebase adds localhost by default. A Horizon host isn't on the list until you add it.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • A Firebase project with a sign-in provider enabled. This page uses Google.
  • A web app that uses the Firebase JavaScript SDK

Start a tunnel

Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so the URLs you save in Firebase would point at a dead URL after a restart. Reserved subdomains are a paid feature, see Pricing.

hrzn tunnel http://localhost:3000 -s my-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Your public URL is https://my-app.hrzn.run. Keep this terminal open.

The first time you open the URL in a browser, Horizon shows a Before you continue page. Select Continue to site. You see it once every 7 days per IP address, so the redirect back from Firebase won't surprise you later.

Add the domain in Firebase

  1. Open the Firebase console and select your project.
  2. Open Authentication, then Settings.
  3. Select Authorized domains, then add my-app.hrzn.run.

Enter the host only. Leave out https:// and any port.

Set up the app

Install the SDK.

npm install firebase

Initialize Firebase with the config from your project settings. Firebase's own web setup uses a firebaseConfig object, so there are no environment variables to set.

lib/firebase.ts
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  // Your config values here
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);

Sign in with a popup. The popup flow works with the default authDomain of your Firebase project, so the tunnel host only needs to be an authorized domain.

app/sign-in-button.tsx
"use client";

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

export function SignInButton() {
  const signIn = async () => {
    const result = await signInWithPopup(auth, new GoogleAuthProvider());
    console.log(result.user.uid);
  };

  return <button onClick={signIn}>Sign in with Google</button>;
}

Render SignInButton on a page, then start the app on port 3000.

npm run dev

If you use the redirect flow

signInWithRedirect() is the harder path on a custom host. Firebase says it relies on a cross-origin iframe to your project's Firebase Hosting domain, and that this fails in browsers that block third-party storage access. Firebase's fix for a custom domain is to set that domain as authDomain. This makes the app and the auth iframe share a domain.

  1. Set authDomain to my-app.hrzn.run in firebaseConfig.
  2. Add https://my-app.hrzn.run/__/auth/handler to the authorized redirect URIs of each OAuth provider.
  3. Make your app serve the Firebase helper files under /__/auth/. Firebase lists two ways: forward requests for /__/auth/ to https://<project>.firebaseapp.com/__/auth/ with a reverse proxy, or self-host the helper files. Self-hosting doesn't work for Apple sign-in or SAML.

Use the popup for local testing unless you need to test the redirect flow itself.

Sign in through the public URL

Open https://my-app.hrzn.run and select Sign in with Google. Start from the public URL, because Firebase checks the domain you sign in from.

Check it works

The Google popup opens, and it closes after you pick an account. The uid of the signed-in user prints in the browser console.

Your Horizon terminal prints a GET line for the page you loaded. Firebase handles the sign-in handshake itself, so you won't see a callback line unless you use the redirect flow.

Troubleshooting

Firebase says the domain isn't authorized

Firebase returns auth/unauthorized-domain when the domain of the sign-in isn't on the authorized domains list. Add my-app.hrzn.run under Authentication, Settings, Authorized domains. Enter the host without https:// or a port.

Sign-in with redirect fails or loops

Your browser may block third-party storage, which breaks the default redirect flow. Set authDomain to my-app.hrzn.run and serve /__/auth/, as above. Or switch to signInWithPopup().

The provider rejects the redirect URI

With a custom authDomain, add https://my-app.hrzn.run/__/auth/handler to the provider's authorized redirect URIs.

The tunnel URL changed

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.

Nothing reaches your app

  • Check that the Horizon terminal is still running. If its last line is Connection lost. Reconnecting…, wait for Reconnected.
  • Check that your app listens on port 3000.

Next steps

On this page