Test Sign in with Apple locally
Test Sign in with Apple on localhost with a public HTTPS Return URL from a Horizon tunnel and Auth.js, since Apple rejects localhost.
Test Sign in with Apple on your laptop. Apple needs a public HTTPS domain and Return URL, and a Horizon tunnel provides both.
Auth.js's Apple docs state it plainly: "Apple does not support localhost/http URLs. You can only use a live URL with HTTPS." A reserved Horizon subdomain is a live HTTPS URL that stays the same across restarts.
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) - An Apple Developer account with access to Certificates, Identifiers & Profiles
- An App ID to use as the primary App ID for your website
Start a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so the domain and Return URL you register with Apple 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.
Create the Services ID in Apple
- In Certificates, Identifiers & Profiles, select Identifiers in the sidebar, then select the add button (+).
- Select Services ID, then select Continue.
- Enter a Description and a unique Identifier. Register the Services ID. You use the identifier as the client ID.
- Select the Services ID from the list. Select Sign in with Apple, then select Configure.
- In the modal, select your App ID from the list of primary App IDs.
- In the Website URLs section, set Domains and Subdomains to
my-app.hrzn.run. Leave outhttps://. - Set Return URLs to
https://my-app.hrzn.run/api/auth/callback/apple. - Select Done, then Continue, then review and select Save.
Apple says you don't need to upload a file to your server to register domains and subdomains.
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.
Apple requires the client secret to be a JWT (a signed token). Auth.js provides a command that asks for your Apple details, generates the secret, and adds AUTH_APPLE_ID and AUTH_APPLE_SECRET to your .env file.
npx auth add appleGenerate the Auth.js secret.
npx auth secretAdd the remaining environment variables to the same file.
AUTH_SECRET=replace-with-the-generated-secret
AUTH_URL=https://my-app.hrzn.run
AUTH_TRUST_HOST=true
AUTH_APPLE_ID=replace-with-your-services-id-identifier
AUTH_APPLE_SECRET=replace-with-the-generated-jwtConfigure the provider.
import NextAuth from "next-auth";
import Apple from "next-auth/providers/apple";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Apple],
});Expose the route handlers. This path makes the callback /api/auth/callback/apple.
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("apple");
}}
>
<button type="submit">Sign in with Apple</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 Apple.
Don't start from http://localhost:3000. Apple can't send the user back there.
Check it works
Apple shows its sign-in page. After you approve, your browser lands back on https://my-app.hrzn.run.
Your Horizon terminal prints a line ending in the callback path.
/api/auth/callback/appleApple returns the user's email and name only the first time the user consents to the app, according to Auth.js. A second sign-in test returns no name.
Troubleshooting
Apple rejects localhost or an http URL
Apple doesn't accept localhost or http in Domains and Subdomains or Return URLs, and a 127.0.0.1 address fails later in the flow. Use my-app.hrzn.run and the https:// Return URL from step 2.
Apple returns an error after you approve
- Compare Return URLs with
https://my-app.hrzn.run/api/auth/callback/apple. Check the scheme, host and path. - Check that Domains and Subdomains holds
my-app.hrzn.run. - Check that
AUTH_APPLE_IDis the Services ID identifier, not the App ID. - Check that you restarted
npm run devafter editing.env.local.
The redirect URI in the request 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.
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. Apple still holds the old domain and Return URL. Restart with -s my-app. -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 Apple's guide to configuring Sign in with Apple for the web.