Authentication
Core Idea
Implemente autenticação em Next.js separando três conceitos: Authentication (provar identidade), Session Management (persistir o estado auth entre requests) e Authorization (decidir o que o usuário pode acessar). Para produção, prefira uma biblioteca de auth em vez de construir do zero: os exemplos aqui são educacionais.
Key Concepts
- Authentication: verificação de identidade (login/signup) via
<form> + Server Actions + useActionState, com validação de schema (Zod/Valibot/Yup) no servidor.
- Stateless sessions: dado da sessão (ou token) fica em cookie no browser, assinado/criptografado (ex.: JWT via
jose); mais simples, requer implementação cuidadosa.
- Database sessions: dado da sessão fica no banco, browser só recebe um session ID criptografado; mais seguro, mais complexo, permite revogar sessões/rastrear devices.
cookies() API: define cookies de sessão no servidor com opções recomendadas (httpOnly, secure, sameSite, expires/maxAge, path).
- Optimistic checks (Proxy): leitura da sessão só do cookie (sem hit no banco) para redirecionar rápido; usado em
proxy.ts, roda em toda rota incluindo prefetch, então nunca deve consultar banco.
- Secure checks (DAL): verificação contra o banco de dados; deve ficar o mais perto possível da fonte de dados, nunca só em Proxy ou Layout.
- Data Access Layer (DAL): módulo central de autorização, com função
verifySession() memoizada via React cache(), chamada em todo data request/Server Action/Route Handler.
- Data Transfer Objects (DTO): retornar só os campos necessários (nunca o objeto completo do usuário) para evitar vazar dados sensíveis ao cliente.
- Layouts não são pontos de checagem confiáveis: por causa de Partial Rendering, layouts não re-renderizam a cada navegação, então checagem de sessão ali não roda em toda troca de rota; segmentos e parallel routes ainda renderizam independentemente do layout.
- Auth e streaming:
await cookies()/headers()/DAL no topo de um layout atrasa o primeiro chunk stream; mova o await para um Server Component filho envolto em <Suspense>.
- Client Components não importam a DAL: rode
verifySession()/getUser() num Server Component pai e passe os dados como props/context; use taintUniqueValue do React para impedir que campos sensíveis cheguem ao client.
Code Examples
import 'server-only'
import { SignJWT, jwtVerify } from 'jose'
const secretKey = process.env.SESSION_SECRET
const encodedKey = new TextEncoder().encode(secretKey)
export async function encrypt(payload) {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(encodedKey)
}
export async function decrypt(session) {
try {
const { payload } = await jwtVerify(session, encodedKey, { algorithms: ['HS256'] })
return payload
} catch (error) {
console.log('Failed to verify session')
}
}
- O que demonstra: Criptografia/verificação de sessão JWT com
jose, isolada via server-only.
export async function createSession(userId: string) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
const session = await encrypt({ userId, expiresAt })
const cookieStore = await cookies()
cookieStore.set('session', session, {
httpOnly: true,
secure: true,
expires: expiresAt,
sameSite: 'lax',
path: '/',
})
}
- O que demonstra: Setar o cookie de sessão com as opções de segurança recomendadas (httpOnly, secure, sameSite).
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function proxy(req: NextRequest) {
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
if (isPublicRoute && session?.userId && !req.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
- O que demonstra: Checagem otimista de auth no Proxy, lendo só o cookie (nunca o banco) para redirecionar entre rotas protegidas/públicas.
import 'server-only'
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
export const verifySession = cache(async () => {
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
if (!session?.userId) {
redirect('/login')
}
return { isAuth: true, userId: session.userId }
})
- O que demonstra: DAL central com
verifySession() memoizado por React cache(), usado como ponto único de verdade para checagem segura de sessão.
Reference Tables
| Tipo de sessão | Onde vive | Trade-off |
|---|
| Stateless | Cookie do browser (assinado/criptografado) | Mais simples, risco se mal implementado |
| Database | Banco, browser só tem session ID criptografado | Mais seguro, mais complexo/custoso |
| Tipo de checagem | Fonte | Uso |
|---|
| Optimistic | Cookie (sem DB) | Proxy, redirects rápidos, UI show/hide |
| Secure | Banco de dados | Mutations, dados sensíveis, DAL |
| Camada | Onde checar autorização |
|---|
| Server Components | Role-based rendering condicional |
| Layouts | NÃO confiável sozinho, não re-renderiza a cada navegação |
| Server Actions | Tratar como endpoint público, checar sessão + role |
| Route Handlers | Tratar como endpoint público, 401 sem sessão / 403 sem permissão |
Anti-patterns
return null em layout/componente top-level para "esconder" conteúdo não autorizado: não impede acesso a segmentos aninhados nem a Server Actions, Next.js tem múltiplos entry points; a checagem real precisa estar na DAL.
- Consultar o banco de dados dentro do Proxy: Proxy roda em toda rota, incluindo prefetch; checagens devem ser só otimistas (cookie), nunca DB, sob risco de degradar performance.
- Confiar em checagem de auth só no Layout: Partial Rendering faz o layout não re-renderizar a cada navegação; segmentos e parallel routes continuam acessíveis.
- Retornar objeto de usuário inteiro em vez de DTO: risco de vazar senha/hash, telefone, dados sensíveis ao client.
Key Takeaways
- Separe Authentication, Session Management e Authorization como três responsabilidades distintas; use bibliotecas de auth prontas para produção real.
- Centralize checagens em uma DAL com
verifySession() memoizado, chamada em todo data request/Server Action/Route Handler; nunca confie só em Proxy ou Layout.
- Proxy é só para checagens otimistas via cookie (nunca DB); é a primeira linha de defesa, não a única.
- Use DTOs para retornar apenas os campos necessários, nunca o objeto completo do usuário.
- Para não atrasar o streaming, empurre
await cookies()/DAL para dentro de um Server Component filho envolto em <Suspense>.
Connects To
- proxy (ch017): implementação técnica do Proxy usado para optimistic checks.
- authentication-with-cache-components: regras específicas de leitura de sessão e cache por-usuário quando Cache Components está ativado.
- server-actions: tratamento de Server Actions como endpoints públicos que precisam de autorização própria.
- streaming: padrão "push dynamic access down" para não bloquear o shell com leituras de sessão.