Horizon

Test Facebook Login locally

Test Facebook Login on localhost with a stable HTTPS redirect URI from a Horizon tunnel and Auth.js.

Sign in with Facebook on your laptop while Facebook redirects to a stable HTTPS URL.

Before you begin

  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • A Next.js App Router app with Auth.js installed (npm install next-auth@beta)
  • A Facebook app with the Facebook Login product added. Use a dedicated development app, as Auth.js recommends for non-production testing.

Start a tunnel

Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so the redirect URI you register in Facebook 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.

Configure Facebook Login

  1. Open your app in the Meta App Dashboard.
  2. Go to Products, then Facebook Login, then Settings.
  3. Under Client OAuth Settings, add https://my-app.hrzn.run/api/auth/callback/facebook to Valid OAuth Redirect URIs.
  4. Check that Client OAuth Login and Web OAuth Login are on.
  5. Save the changes.
  6. Go to App settings, then Basic. Add my-app.hrzn.run to App domains.
  7. On the same page, copy the App ID and the App Secret.

Facebook's Strict Mode requires an exact match with your Valid OAuth Redirect URIs list. The full URI must match, including parameters, except the optional state parameter.

Configure Auth.js

Auth.js builds the redirect URI from the request. Horizon sets Host to localhost:3000 and sends the public host in X-Forwarded-Host. AUTH_TRUST_HOST=true tells Auth.js to trust X-Forwarded-Host. AUTH_URL pins the public URL. Auth.js says AUTH_URL is mostly unnecessary in v5, so treat it as a second guard.

Generate the secret.

npx auth secret

Add the environment variables. Auth.js uses the App ID as the client ID and the App Secret as the client secret.

.env.local
AUTH_SECRET=replace-with-the-generated-secret
AUTH_URL=https://my-app.hrzn.run
AUTH_TRUST_HOST=true
AUTH_FACEBOOK_ID=replace-with-your-app-id
AUTH_FACEBOOK_SECRET=replace-with-your-app-secret

Configure the provider.

auth.ts
import NextAuth from "next-auth";
import Facebook from "next-auth/providers/facebook";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [Facebook],
});

Expose the route handlers. This path makes the callback /api/auth/callback/facebook.

app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;

Add a sign-in button.

app/page.tsx
import { signIn } from "@/auth";

export default function Home() {
  return (
    <form
      action={async () => {
        "use server";
        await signIn("facebook");
      }}
    >
      <button type="submit">Sign in with Facebook</button>
    </form>
  );
}

Start the app on port 3000.

npm run dev

Sign in through the public URL

Open https://my-app.hrzn.run in your browser. On the first visit, select Continue to site on the Before you continue page. Horizon shows this page once every 7 days per IP address. Then select Sign in with Facebook.

Don't start from http://localhost:3000. Your app would then send a different origin to Facebook.

Check it works

Facebook shows its login dialog. After you approve, your browser lands back on https://my-app.hrzn.run.

Your Horizon terminal prints a line for the callback. The status is a 3xx, because Auth.js redirects after it sets the session.

Output
  GET     302  /api/auth/callback/facebook

Auth.js notes that the email can be missing for accounts created on mobile.

Troubleshooting

URL Blocked: This redirect failed because the redirect URI is not whitelisted

Facebook shows this message when the redirect URI isn't in your app's Client OAuth Settings.

  • Compare Valid OAuth Redirect URIs with https://my-app.hrzn.run/api/auth/callback/facebook. The match is exact.
  • Check that Client OAuth Login and Web OAuth Login are on.
  • Check that App domains holds my-app.hrzn.run.
  • Check that the subdomain in Facebook matches the one you pass to -s.

The redirect URI in the error contains localhost

Your app built the redirect URI from the Host header, which Horizon sets to localhost:3000. Set AUTH_TRUST_HOST=true and AUTH_URL=https://my-app.hrzn.run in .env.local. Restart npm run dev, because Next.js reads .env.local at start.

Auth.js throws an UntrustedHost error

Auth.js requires trustHost to be true because it relies on the request's host header. Set AUTH_TRUST_HOST=true in .env.local and restart npm run dev.

The URL changed after a restart

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

The browser shows a Before you continue page

Horizon shows this page once every 7 days per IP address. Select Continue to site.

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 the app listens on port 3000, the port in your tunnel command.

Next steps

On this page