Horizon

Test GitLab OAuth login locally

Sign in with GitLab on localhost by registering a stable HTTPS Redirect URI from a Horizon tunnel, with Auth.js in Next.js.

Test "Sign in with GitLab" on your laptop, with a Redirect URI GitLab can reach.

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 GitLab account
  • A Next.js App Router app with Auth.js set up. The Auth.js guide covers AUTH_SECRET and the route handler.

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-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

Keep this terminal open.

Register the application in GitLab

  1. In the upper-right corner of GitLab, select your avatar, then Edit profile.
  2. In the left sidebar, select Access, then Applications.
  3. Add a new application. Set Name to something you recognize, such as my-app local.
  4. Set Redirect URI to https://my-app.hrzn.run/api/auth/callback/gitlab.
  5. Under OAuth 2 Scopes, select read_user. Auth.js needs it to save the user's email address on sign up.
  6. Save the application. GitLab shows the Application ID and a Secret. Copy both.

GitLab says you can use a non-SSL URL but should use an SSL one. A tunnel URL is already HTTPS.

Configure Auth.js

Auth.js reads the GitLab credentials from AUTH_GITLAB_ID and AUTH_GITLAB_SECRET. AUTH_TRUST_HOST=true makes Auth.js trust X-Forwarded-Host, so it builds the callback from the public host. Horizon sets Host to localhost:3000.

.env.local
AUTH_SECRET=replace-with-the-generated-secret
AUTH_TRUST_HOST=true
AUTH_GITLAB_ID=replace-with-the-application-id
AUTH_GITLAB_SECRET=replace-with-the-secret
auth.ts
import NextAuth from "next-auth";
import GitLab from "next-auth/providers/gitlab";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitLab],
});
app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;

For a self-managed GitLab, pass its address: GitLab({ baseUrl: "https://gitlab.example.com" }).

Add a sign-in button, then start the app on port 3000.

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

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

Sign in

Open https://my-app.hrzn.run. On the first visit, select Continue to site on the Before you continue page. Then select Sign in with GitLab.

Check it works

GitLab shows its authorization screen. After you approve, your browser lands back on https://my-app.hrzn.run.

Your Horizon terminal prints a line for the callback:

Output
  GET     302  /api/auth/callback/gitlab

Troubleshooting

GitLab rejects the redirect URI

GitLab requires the redirect_uri in the authorization request to match the one in the token request. It also expects it to match the application's Redirect URI.

  • Check that Redirect URI is exactly https://my-app.hrzn.run/api/auth/callback/gitlab.
  • Check that AUTH_TRUST_HOST=true is set and that you restarted npm run dev. Without it, Auth.js can build a localhost callback.
  • Check that the subdomain matches the one you pass to -s.

The user has no email address

Select the read_user scope in the GitLab application. Auth.js documents it as the scope that saves the email on sign up.

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 Redirect URI stays 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 for Reconnected.
  • Check that the app listens on port 3000, the port in your tunnel command.

Next steps

On this page