Test Auth.js (NextAuth v5) login behind a tunnel
Run Auth.js sign-in on localhost behind a Horizon tunnel, with AUTH_TRUST_HOST, AUTH_URL and the right callback path.
Sign in with Auth.js on your laptop while the provider redirects to a stable HTTPS URL.
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 Next.js App Router app with Auth.js installed (
npm install next-auth@beta) - An OAuth app at a provider. This page uses GitHub. For other providers, see Test OAuth redirect URLs locally.
Set up Auth.js
Auth.js builds its callback URL from the request headers. Behind a tunnel, Horizon sets Host to localhost:3000 and sends the public host in X-Forwarded-Host. Set AUTH_TRUST_HOST so Auth.js trusts X-Forwarded-Host. Auth.js documents this for apps behind a reverse proxy.
Generate the secret.
npx auth secretAdd the environment variables.
AUTH_SECRET=replace-with-the-generated-secret
AUTH_TRUST_HOST=true
AUTH_GITHUB_ID=replace-with-your-client-id
AUTH_GITHUB_SECRET=replace-with-your-client-secretConfigure Auth.js with a provider.
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
});You can set trustHost: true in this config instead of the environment variable. Pick one.
Expose the route handlers. The path decides your callback URL.
import { handlers } from "@/auth";
export const { GET, POST } = handlers;Add a sign-in button.
import { signIn } from "@/auth";
export default function Home() {
return (
<form
action={async () => {
"use server";
await signIn("github");
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
);
}Start the app on port 3000.
npm run devStart 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 would point at a dead URL after a restart. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appKeep this terminal open.
Register the callback URL
Auth.js serves the callback at /api/auth/callback/<provider id> in Next.js. For GitHub the redirect URI is:
https://my-app.hrzn.run/api/auth/callback/githubRegister it in the provider. The GitHub guide shows where. For GitLab and Twitch, see GitLab and Twitch.
Sign in
Open https://my-app.hrzn.run in your browser. On the first visit, select Continue to site on the Before you continue page. Then select Sign in with GitHub.
Check it works
The provider shows its consent screen. 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.
GET 302 /api/auth/callback/githubSetting AUTH_URL instead
Auth.js v5 infers the host from the request headers and says AUTH_URL is mostly unnecessary. If you prefer to pin the public URL, set it in .env.local and restart the dev server.
AUTH_URL=https://my-app.hrzn.runAuth.js documents AUTH_URL for apps with a different base path. For a tunnel, AUTH_TRUST_HOST=true is the documented fix. Use it first.
Troubleshooting
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, or trustHost: true in the NextAuth config. Restart npm run dev.
The provider says the redirect URI doesn't match
- Compare the URI registered at the provider with
https://my-app.hrzn.run/api/auth/callback/<provider id>. The scheme, host and path must match. - Check that the subdomain in the provider matches the one you pass to
-s. - Check that
AUTH_TRUST_HOST=trueis set and that you restarted the dev server. Without it, the callback can resolve tolocalhost.
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 forReconnected. - Check that the app listens on port 3000, the port in your tunnel command.
Next steps
- Read Auth.js deployment notes for
AUTH_URLandAUTH_TRUST_HOST. - Test the same flow with Better Auth.
Test Twitch OAuth login locally
Sign in with Twitch on localhost by registering a stable HTTPS OAuth Redirect URL from a Horizon tunnel, with Auth.js in Next.js.
Test Better Auth social login behind a tunnel
Run Better Auth social sign-in on localhost behind a Horizon tunnel, with baseURL, trustedOrigins and the right callback path.