//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Proteja páginas e rotas de API em uma aplicação Next.js 15+ App Router usando verificações de sessão baseadas em middleware, validação de autenticação no lado do servidor e proteção por página.
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
const protectedPaths = ["/dashboard", "/settings", "/account"];
export function middleware(request: NextRequest) {
const sessionToken = request.cookies.get("session-token")?.value;
const { pathname } = request.nextUrl;
const isProtected = protectedPaths.some((path) =>
pathname.startsWith(path)
);
if (isProtected && !sessionToken) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/settings/:path*", "/account/:path*"],
};// app/dashboard/layout.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { verifySession } from "@/lib/auth";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const cookieStore = await cookies();
const token = cookieStore.get("session-token")?.value;
if (!token) {
redirect("/login");
}
const session = await verifySession(token);
if (!session) {
redirect("/login");
}
return <>{children}</>;
}// lib/auth.ts
import "server-only";
import { SignJWT, jwtVerify } from "jose";
const secret = new TextEncoder().encode(process.env.AUTH_SECRET);
export async function createSession(userId: string) {
return new SignJWT({ userId })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("7d")
.sign(secret);
}
export async function verifySession(token: string) {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch {
return null;
}
}// app/login/actions.ts
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { createSession } from "@/lib/auth";
export async function login(formData: FormData) {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// Valide as credenciais contra seu banco de dados
const user = await authenticateUser(email, password);
if (!user) {
return { error: "Credenciais inválidas" };
}
const token = await createSession(user.id);
const cookieStore = await cookies();
cookieStore.set("session-token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7, // 7 dias
path: "/",
});
redirect("/dashboard");
}server-only garante que lib/auth.ts nunca possa ser incluído no código do cliente, evitando vazamento de segredos.cookies() é assíncrona no Next.js 15+ e deve ser aguardada.redirect() lança internamente, então o código após ele é inalcançável. Nenhum return explícito é necessário após redirect().Usando NextAuth.js (Auth.js v5):
// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
});
// middleware.ts
export { auth as middleware } from "./auth";Acesso Baseado em Papel:
// app/admin/layout.tsx
import { verifySession } from "@/lib/auth";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const cookieStore = await cookies();
const token = cookieStore.get("session-token")?.value;
const session = await verifySession(token!);
if (session?.role !== "admin") {
redirect("/unauthorized");
}
return <>{children}</>;
}cookies().get() retorna { name: string; value: string } | undefined. Sempre use encadeamento opcional.{ error: string }) não podem chamar redirect() no mesmo caminho de código, porque redirect() lança um erro.import "server-only" no topo de qualquer módulo que contenha segredos para obter um erro em tempo de compilação se ele for importado de um Client Component.cookies() é assíncrono no Next.js 15+. Esquecer await produz um erro em tempo de execução.matcher do middleware deve ser estático. Você não pode usar variáveis de tempo de execução no array config.matcher. Use lógica condicional dentro da função de middleware em vez disso.redirect() dentro de um try/catch será engolido porque ele lança um erro especial NEXT_REDIRECT. Re-lance-o ou chame redirect() fora do try/catch.export const dynamic = "force-dynamic" ou execute a autenticação no middleware para páginas geradas estaticamente.| Abordagem | Prós | Contras |
|---|---|---|
| Apenas Middleware | Rápido, executa antes da renderização | Não pode verificar tokens contra o banco de dados |
| Autenticação Baseada em Layout | Acesso completo ao servidor, pode consultar o banco de dados | Executa após o middleware, leve latência |
| NextAuth.js (Auth.js) | Provedores integrados, gerenciamento de sessão | Dependência extra, mudanças de migração |
| Clerk ou Supabase Auth | Serviço gerenciado, menos código | Bloqueio de fornecedor, custo em escala |
| Iron Session | Cookies criptografados, sem JWT | Comunidade menor, configuração manual |
cookies() é assíncrono no Next.js 15+ e retorna uma Promise.await significa que você chama .get() em uma Promise, que retorna undefined.AUTH_SECRET e a lógica de assinatura JWT vazem para o bundle do navegador.redirect() lança internamente um erro especial NEXT_REDIRECT.NEXT_REDIRECT ou chame redirect() fora do try/catch./login.pathname original é anexado como ?callbackUrl=/dashboard (ou qualquer que seja o caminho solicitado).httpOnly impede que o JavaScript leia o cookie, mitigando ataques XSS.secure garante que o cookie seja enviado apenas via HTTPS em produção.sameSite: "lax" fornece proteção básica contra CSRF.config.matcher deve ser estaticamente analisável em tempo de compilação.import { JWTPayload } from "jose";
export async function verifySession(
token: string
): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch {
return null;
}
}JWTPayload de jose fornece o tipo base com iss, sub, exp, etc.null em caso de falha para que os chamadores possam fazer uma verificação simples de truthiness.export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
// ...
}React.ReactNode para a prop children, que cobre elementos, strings, números, fragmentos e null.export const dynamic = "force-dynamic" ou proteja a rota via middleware em vez disso.auth as middleware diretamente, o que lida com verificações de sessão automaticamente.AUTH_SECRET com segurançaRevisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥