Test Microsoft sign-in locally
Test Microsoft Entra ID sign-in on localhost with a stable HTTPS redirect URI from a Horizon tunnel and Auth.js.
Sign in with Microsoft on your laptop while Microsoft Entra ID 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 Microsoft Entra tenant where you can register applications. Microsoft requires at least the Application Developer role.
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 Entra 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-appYour public URL is https://my-app.hrzn.run. Keep this terminal open.
Register the app in Microsoft Entra
- Sign in to the Microsoft Entra admin center.
- Go to Entra ID, then App registrations, and select New registration.
- Enter a Name.
- Under Supported account types, select Any Entra ID Tenant + Personal Microsoft accounts. Auth.js defaults its issuer to
https://login.microsoftonline.com/common/v2.0, which fits this choice. - Select Register.
- On the Overview page, copy the Application (client) ID.
- Select Authentication. Under Platform configurations, add a Web platform and set its redirect URI to
https://my-app.hrzn.run/api/auth/callback/microsoft-entra-id. If a Web platform exists, select Add URI instead. - Save the change.
- Create a client secret for the app and copy its value.
Microsoft requires redirect URIs to start with https, with exceptions for some localhost URIs. A Horizon URL is HTTPS, so it needs no exception. Microsoft says redirect URIs are case-sensitive.
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 secretAdd the environment variables.
AUTH_SECRET=replace-with-the-generated-secret
AUTH_URL=https://my-app.hrzn.run
AUTH_TRUST_HOST=true
AUTH_MICROSOFT_ENTRA_ID_ID=replace-with-your-application-client-id
AUTH_MICROSOFT_ENTRA_ID_SECRET=replace-with-your-client-secretConfigure the provider.
import NextAuth from "next-auth";
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [MicrosoftEntraID],
});Expose the route handlers. This path makes the callback /api/auth/callback/microsoft-entra-id.
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("microsoft-entra-id");
}}
>
<button type="submit">Sign in with Microsoft</button>
</form>
);
}Start the app on port 3000.
npm run devSign 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 Microsoft.
Don't start from http://localhost:3000. Your app would then send a different origin to Microsoft.
Check it works
Microsoft shows its sign-in page. 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/microsoft-entra-idTroubleshooting
AADSTS50011: The redirect URI does not match
Microsoft's message reads: "The redirect URI specified in the request does not match the redirect URIs configured for the application."
- Compare the URI under Authentication with
https://my-app.hrzn.run/api/auth/callback/microsoft-entra-id. The path is case-sensitive. - Check that you added it to a Web platform.
- Check that the subdomain in Entra matches the one you pass to
-s. - Wait three to five minutes after you save. Microsoft says changes need that long.
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 forReconnected. - Check that the app listens on port 3000, the port in your tunnel command.
Next steps
- Read the shared notes on OAuth redirect URLs and the Auth.js guide.
- Read Microsoft's redirect URI best practices and limitations.
Test Sign In with LinkedIn locally
Test Sign In with LinkedIn using OpenID Connect on localhost with a stable HTTPS redirect URL from a Horizon tunnel and Auth.js.
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.