Next.js + Auth.js v5
Agrega autenticación a una app de Next.js 15 con Auth.js v5 (antes conocido como NextAuth), incluida OAuth de GitHub y protección de rutas basada en sesiones.
Busca en todas las páginas de la documentación
Agrega autenticación a una app de Next.js 15 con Auth.js v5 (antes conocido como NextAuth), incluida OAuth de GitHub y protección de rutas basada en sesiones.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
npm install next-auth@beta.env.local:
npx auth secretauth.ts en la raíz del proyecto exportando handlers configurados, auth, signIn, y signOut.app/api/auth/[...nextauth]/route.ts que re-exporta los handlers.AUTH_GITHUB_ID, AUTH_GITHUB_SECRET) a .env.local.middleware.ts que usa la exportación auth para proteger rutas..env.localAUTH_SECRET=<32+ char string from `npx auth secret`>
AUTH_GITHUB_ID=<github oauth app client id>
AUTH_GITHUB_SECRET=<github oauth app client secret>
auth.ts (raíz del proyecto)import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
export const \{ handlers, auth, signIn, signOut \} = NextAuth(\{
providers: [GitHub],
session: \{ strategy: "jwt" \},
callbacks: \{
authorized: async (\{ auth \}) => !!auth,
\},
\});app/api/auth/[...nextauth]/route.tsexport \{ GET, POST \} from "@/auth";Espera - en v5 exportas desde el objeto handlers en su lugar:
import \{ handlers \} from "@/auth";
export const \{ GET, POST \} = handlers;middleware.tsexport \{ auth as middleware \} from "@/auth";
export const config = \{
matcher: ["/dashboard/:path*"],
\};app/dashboard/page.tsximport \{ auth, signOut \} from "@/auth";
export default async function DashboardPage() \{
const session = await auth();
return (
<main className="mx-auto max-w-xl p-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-4">Welcome, \{session?.user?.name\}</p>
<form
action=\{async () => \{
"use server";
await signOut();
\}\}
>
<button type="submit" className="mt-4 rounded bg-black px-4 py-2 text-white">
Sign out
</button>
</form>
</main>
);
\}app/page.tsx (botón de inicio de sesión)import \{ auth, signIn \} from "@/auth";
export default async function Home() \{
const session = await auth();
if (session?.user) \{
return <a href="/dashboard">Go to dashboard</a>;
\}
return (
<form
action=\{async () => \{
"use server";
await signIn("github", \{ redirectTo: "/dashboard" \});
\}\}
>
<button type="submit">Sign in with GitHub</button>
</form>
);
\}Auth.js v5 centraliza la configuración en un archivo auth.ts raíz, que exporta todas las funciones que necesitas en otros lugares. La función auth() es isomórfica - lee la sesión desde cookies en Server Components, Server Actions, Route Handlers, y middleware. La importación middleware export \{ auth as middleware \} convierte Auth.js en un guardia consciente de matcher que redirige a usuarios no autenticados a la página de inicio de sesión.
Bajo el capó, Auth.js establece una cookie JWT firmada y encriptada por defecto (session.strategy: "jwt"). Puedes cambiar a sesiones de base de datos agregando un adaptador Prisma/Drizzle.
Google, Credentials, Email, etc. al array providers.@auth/prisma-adapter, pasa adapter: PrismaAdapter(prisma), y establece session: \{ strategy: "database" \}.pages: \{ signIn: "/login" \} en la configuración de NextAuth y construye tu propia página /login que llama a signIn().jwt y session para incluir un claim role, luego verifica en middleware o Server Components.auth() en RSC vs useSession() en cliente: el código de servidor usa await auth(); el código de cliente envuelve la app en <SessionProvider> y llama a useSession().auth.config.ts libre de adaptadores solo para Node e importa solo la configuración ligera en middleware.Extiende la interfaz Session usando module augmentation para que tus claims personalizados estén tipados en todos lados:
// types/next-auth.d.ts
import \{ DefaultSession \} from "next-auth";
declare module "next-auth" \{
interface Session \{
user: \{
id: string;
role: "admin" | "user";
\} & DefaultSession["user"];
\}
\}Después de esto, session.user.role está fuertemente tipado en todos lados donde llames a auth().
AUTH_SECRET debe ser lo suficientemente largo. Auth.js v5 requiere 32+ caracteres. Genera con npx auth secret - no lo escribas a mano.fs, crypto.randomBytes en forma heredada, controladores de base de datos). Mantén la lógica pesada fuera de middleware.ts./_next/static e imágenes si no tienes cuidado. Usa una negative lookahead como `/((?!api|_next/static|_next/image|favicon.ico).*)``.https://yourapp.com/api/auth/callback/github. Una barra final o protocolo incorrecto rompe el inicio de sesión con un error vago.http://localhost:3000 y otra para producción.auth.ts en la raíz del proyecto, no en app/. Ponerlo dentro de app/ hace que Next.js lo trate como una ruta. Mantenlo en la raíz del proyecto o bajo lib/ y alias en consecuencia.| Herramienta | Estilo | Mejor para |
|---|---|---|
| Auth.js (NextAuth v5) | OSS, integrado con framework | La mayoría de apps de Next.js, flujos OAuth |
| Clerk | SaaS pago | UI pulida, configuración rápida, orgs/teams |
| Lucia Auth | DIY ligero | Control total, aprendizaje, flujos personalizados |
| Supabase Auth | Incluido con Supabase DB | Apps que ya usan Supabase |
| Kinde | SaaS pago | Feature flags + auth combinado |
| Custom JWT | Hecho a tu medida | Requisitos especializados |
En la raíz del proyecto (al mismo nivel que next.config.js) o bajo lib/auth.ts. No lo pongas dentro del directorio app/ - Next.js interpretará archivos allí como rutas.
Ejecuta npx auth secret. Escribe automáticamente un valor de 32+ caracteres criptográficamente aleatorio en .env.local. No reutilices secretos entre entornos.
JWT es sin estado, rápido, y el predeterminado - perfecto para la mayoría de apps. Las sesiones de base de datos requieren un adaptador pero te dan revocación instantánea, almacenamiento más grande, y fácil listado de sesiones entre dispositivos. Elige sesiones de base de datos para apps con cierre de sesión en todas partes o funciones de expulsión de usuarios del administrador.
Establece pages: \{ signIn: "/login" \} en la configuración de NextAuth, luego construye app/login/page.tsx que renderiza tu propia UI y llama a signIn("github") desde una Server Action.
import \{ auth \} from "@/auth";
const session = await auth();
if (!session?.user) redirect("/login");En Client Components, envuelve la app en <SessionProvider> y llama a useSession() en su lugar.
El bundle de middleware usa el runtime Edge, que no puede incluir código solo para Node como el adaptador Prisma. Divide tu configuración: pon la lista de proveedores ligera en auth.config.ts e importa solo eso en middleware.ts. Mantén el adaptador en el auth.ts completo que usan los Server Components.
Casi siempre es un desajuste de URL de callback. Tu app OAuth de GitHub (o Google) debe listar la URL de producción exacta: https://yourapp.com/api/auth/callback/github. Cuidado con las barras finales, http vs https, y el subdominio www.
Usa el callback jwt para grabar el rol en el token, luego reflújalo en la sesión:
callbacks: \{
async jwt(\{ token, user \}) \{
if (user) token.role = user.role;
return token;
\},
async session(\{ session, token \}) \{
session.user.role = token.role as "admin" | "user";
return session;
\},
\}Usa module augmentation en types/next-auth.d.ts:
declare module "next-auth" \{
interface Session \{
user: \{ id: string; role: string \} & DefaultSession["user"];
\}
\}Asegúrate de que types/next-auth.d.ts esté incluido en tu tsconfig.json.
auth() devuelve Session | null, e incluso cuando no es nulo, user es opcional. Siempre estrecha:
const session = await auth();
if (!session?.user) redirect("/login");Después de ese check, session.user se estrecha al tipo definido.
Llama a signOut() desde Auth.js. En un Server Component lo envuelves en una Server Action:
<form action=\{async () => \{ "use server"; await signOut(); \}\}>
<button type="submit">Cerrar sesión</button>
</form>En un Client Component, importa signOut desde next-auth/react y llama directamente.
Clerk es más rápido de configurar e incluye UI pulida, orgs, y MFA de caja - pero es de pago. Auth.js es gratis y totalmente personalizable pero requiere más cableado. Elige Clerk si la velocidad de entrada al mercado y las características importan más que el costo; elige Auth.js si quieres control total o cero vendor lock-in.
Revisado por Chris St. John·Última actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥