Guide · Auth0 + Next.js App Router

Bots never reach Auth0.
Verify humans first.

Auth0's Universal Login is hosted on Auth0's domain — you can't run your own verification there. So don't: verify the human on your page, where behavioral signals live, and only then reveal the signup redirect. Unverified sessions never even learn the route. Every snippet below was live-tested on Next.js 16 + @auth0/nextjs-auth0 v4 + usehuma 1.2.1.

01Install & environment

npm install @auth0/nextjs-auth0 usehuma

Create a Regular Web Application in your Auth0 dashboard and copy its values from the Settings tab. You need usehuma@1.2.1+.

# .env.local — values from your Auth0 application's Settings tab
AUTH0_DOMAIN=dev-YOUR-TENANT.us.auth0.com
AUTH0_CLIENT_ID=••••••••
AUTH0_CLIENT_SECRET=••••••••
AUTH0_SECRET=$(openssl rand -hex 32)
APP_BASE_URL=http://localhost:3000
NEXT_PUBLIC_HUMA_KEY=huma_live_••••••••

02Auth0 client + proxy

// lib/auth0.ts
import { Auth0Client } from "@auth0/nextjs-auth0/server";

export const auth0 = new Auth0Client(); // reads the env vars above
// proxy.ts  (Next.js 16 — replaces middleware.ts)
import type { NextRequest } from "next/server";
import { auth0 } from "./lib/auth0";

export async function proxy(request: NextRequest) {
  return await auth0.middleware(request); // mounts /auth/login, /auth/callback…
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
};
Two gotchas we hit live: (1) Next.js 16 uses proxy.tsmiddleware.ts is deprecated for the Node runtime. (2) The Auth0 middleware intercepts GET requests — if you smoke-test with curl -I (a HEAD request) you'll get a confusing 404. Use a plain GET and you'll see the 307 to your tenant.

03Configure callback URLs

In your Auth0 application → Settings → Application URIs:

Allowed Callback URLs:  http://localhost:3000/auth/callback
Allowed Logout URLs:    http://localhost:3000

(Add your production URLs alongside when you deploy.)

04Gate the signup redirect

// app/page.tsx — verify before showing the signup link
"use client";
import { useEffect, useState } from "react";

export default function Home() {
  const [human, setHuman] = useState<boolean | null>(null);

  useEffect(() => {
    const s = document.createElement("script");
    s.src = "https://humaverify.com/huma.js";
    s.onload = () => setTimeout(async () => {
      // Your own route holds the API key. Never ship it to the browser.
      const r = await fetch("/api/huma-check", {
        method: "POST",
        body: JSON.stringify({
          userId: "auth0_signup_visitor",
          sessionData: (window as any).Huma.debug().features,
        }),
      }).then((r) => r.json()).catch(() => ({ human: true }));
      setHuman(r.human);
    }, 3000);
    document.head.appendChild(s);
  }, []);

  if (human === null) return <p>Verifying you&apos;re human…</p>;
  if (!human) return <p>Something looks automated. Email us if this is wrong.</p>;
  return <a href="/auth/login?screen_hint=signup">Sign up →</a>;
}
How it behaves: ~4 seconds of invisible signal collection while the visitor reads your page, then the "Sign up" link appears for verified humans and routes through /auth/login?screen_hint=signup straight to Auth0's signup screen. Bots get a retry wall — and the redirect path is never in their DOM.

Auth0 for identity, useHUMA for humanity.

14-day free trial · one API key · zero PII.

Get your API key →
Help