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 usehumaCreate 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.ts —middleware.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'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 →