Test Login with Amazon locally
Test Login with Amazon on localhost by registering a stable HTTPS Allowed Return URL from a Horizon tunnel.
Test "Login with Amazon" on your laptop, with HTTPS URLs Amazon accepts.
Auth.js doesn't list an Amazon provider, so this page uses Amazon's authorization code flow directly in a Next.js App Router app.
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. - An Amazon developer account with access to the Login with Amazon console
- A Next.js App Router app
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 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 URLs in Amazon
Create a security profile, then add your tunnel as a website.
- In the Login with Amazon developer console, select Create a New Security Profile.
- Enter a Name and Description.
- Enter a Consent Privacy Notice URL, then select Save.
- Open Web Settings for the security profile and select Edit.
- Set Allowed Return URLs to
https://my-app.hrzn.run/api/amazon/callback. This is the redirect for the flow on this page. - If you also use the JavaScript SDK popup flow, set Allowed Origins to
https://my-app.hrzn.run. - Select Save, then copy the Client ID and Client Secret.
Amazon requires Allowed Origins to use HTTPS, and it requires an HTTPS redirect URI. A tunnel URL is already HTTPS.
Add the sign-in routes
Store the credentials.
AMAZON_CLIENT_ID=replace-with-your-client-id
AMAZON_CLIENT_SECRET=replace-with-your-client-secret
APP_URL=https://my-app.hrzn.runThe sign-in route sends the browser to Amazon. It builds the redirect_uri from APP_URL, not from the request. Horizon sets the Host header to localhost:3000, so a URI built from the request would point at localhost.
import { randomUUID } from "node:crypto";
import { NextResponse } from "next/server";
export async function GET() {
const state = randomUUID();
const authorizeUrl = new URL("https://www.amazon.com/ap/oa");
authorizeUrl.searchParams.set("client_id", process.env.AMAZON_CLIENT_ID!);
authorizeUrl.searchParams.set("scope", "profile");
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set(
"redirect_uri",
`${process.env.APP_URL}/api/amazon/callback`,
);
authorizeUrl.searchParams.set("state", state);
const response = NextResponse.redirect(authorizeUrl);
response.cookies.set("amazon_state", state, { httpOnly: true, secure: true });
return response;
}The callback route checks state, trades the code for an access token, and reads the profile. Amazon requires the same redirect_uri in the token request.
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const code = request.nextUrl.searchParams.get("code");
const state = request.nextUrl.searchParams.get("state");
const savedState = request.cookies.get("amazon_state")?.value;
if (!code || !state || state !== savedState) {
return new NextResponse("Invalid sign-in response", { status: 400 });
}
const tokenResponse = await fetch("https://api.amazon.com/auth/o2/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: `${process.env.APP_URL}/api/amazon/callback`,
client_id: process.env.AMAZON_CLIENT_ID!,
client_secret: process.env.AMAZON_CLIENT_SECRET!,
}),
});
const { access_token } = await tokenResponse.json();
const profileResponse = await fetch("https://api.amazon.com/user/profile", {
headers: { Authorization: `Bearer ${access_token}` },
});
const profile = await profileResponse.json();
return NextResponse.json(profile);
}The token endpoint above is the North America one. Amazon lists https://api.amazon.co.uk/auth/o2/token for the European Union and https://api.amazon.co.jp/auth/o2/token for the Far East.
Start the app on port 3000.
npm run devSign in
Open https://my-app.hrzn.run/api/amazon/login in your browser. On the first visit, select Continue to site on the Before you continue page. Amazon then shows its sign-in and consent screens.
Check it works
After you approve, your browser lands on https://my-app.hrzn.run/api/amazon/callback and shows your profile as JSON. Amazon documents user_id, name and email in the profile response.
Your Horizon terminal prints a line for the callback:
GET 200 /api/amazon/callbackTroubleshooting
Amazon refuses the redirect URI
- Check that Allowed Return URLs holds
https://my-app.hrzn.run/api/amazon/callback, character for character. - Check that
APP_URLis the public HTTPS URL and that you restartednpm run devafter editing.env.local. - Check that the token request sends the same
redirect_urias the authorization request.
The callback returns Invalid sign-in response
The state value didn't match the cookie, or Amazon sent no code. Start again from /api/amazon/login in the same browser. If the user denied access, Amazon sends an access_denied error instead of a code.
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 URLs stay valid. -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 forReconnected. - Check that the app listens on port 3000, the port in your tunnel command.
Next steps
- Read Amazon's authorization code grant and profile endpoint docs.
- Read the general page, Test OAuth redirect URLs locally.
Test WorkOS AuthKit locally with an HTTPS redirect URI
Test WorkOS AuthKit sign-in on localhost with a Horizon tunnel, using a stable HTTPS redirect URI that WorkOS accepts in a production environment.
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.