Autenticação Completa no Next.js com NextAuth.js e Middleware
O erro que abre a porta: rotas desprotegidas no App Router
A configuração padrão do NextAuth.js protege exatamente zero rotas. Você instala, configura o provider, faz login funcionar na tela e assume que o resto está seguro. Não está. Qualquer rota server-side ou API route continua acessível sem sessão até que você adicione verificação explícita em cada handler ou configure o middleware corretamente.
Esse post monta uma autenticação completa: configuração do NextAuth.js v5 (Auth.js) no App Router, provider OAuth (GitHub) e credentials, sessão JWT, middleware que protege rotas na edge e callbacks que controlam o que vai no token. O resultado é uma base que você copia, adapta o provider e tem proteção real.
Estrutura do projeto e dependências
Antes de qualquer código, instale o necessário:
npm install next-auth@beta @auth/prisma-adapter @prisma/client prisma bcryptjs
npm install -D @types/bcryptjs
A versão next-auth@beta é o Auth.js v5, que suporta App Router nativamente. Se você ainda usa Pages Router, a API é diferente. Este post assume App Router com Next.js 14+.
A estrutura de arquivos que vamos construir:
src/
├── auth.ts # Configuração central do NextAuth
├── middleware.ts # Proteção de rotas na edge
├── app/
│ ├── api/auth/[...nextauth]/route.ts
│ ├── (protected)/
│ │ └── dashboard/page.tsx
│ ├── login/page.tsx
│ └── layout.tsx
├── lib/
│ └── db.ts # Instância do Prisma
└── components/
└── session-provider.tsx
Configuração central do NextAuth.js v5
O arquivo auth.ts na raiz do src é o ponto central. Tudo parte daqui:
// src/auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
import { PrismaAdapter } from "@auth/prisma-adapter";
import bcrypt from "bcryptjs";
import { db } from "@/lib/db";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
// JWT porque o middleware roda na edge e não tem acesso ao banco
session: { strategy: "jwt" },
pages: {
signIn: "/login",
error: "/login",
},
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
Credentials({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Senha", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) return null;
const user = await db.user.findUnique({
where: { email: credentials.email as string },
});
if (!user || !user.hashedPassword) return null;
const passwordMatch = await bcrypt.compare(
credentials.password as string,
user.hashedPassword
);
if (!passwordMatch) return null;
// Retorne apenas o que o NextAuth precisa para montar o token
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
},
}),
],
callbacks: {
// Injeta dados customizados no JWT
async jwt({ token, user }) {
if (user) {
token.role = user.role;
token.id = user.id;
}
return token;
},
// Expõe dados do JWT na sessão do client
async session({ session, token }) {
if (session.user) {
session.user.role = token.role as string;
session.user.id = token.id as string;
}
return session;
},
// Controla redirecionamento pós-login
async authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard && !isLoggedIn) {
return Response.redirect(new URL("/login", nextUrl));
}
return true;
},
},
---
Leia o artigo completo em [https://www.vivodecodigo.com.br/nextjs/nextjs-autenticacao-nextauth-middleware-app-router](https://www.vivodecodigo.com.br/nextjs/nextjs-autenticacao-nextauth-middleware-app-router)