Capítulo 24 de 456

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ãoOnde viveTrade-off
StatelessCookie do browser (assinado/criptografado)Mais simples, risco se mal implementado
DatabaseBanco, browser só tem session ID criptografadoMais seguro, mais complexo/custoso
Tipo de checagemFonteUso
OptimisticCookie (sem DB)Proxy, redirects rápidos, UI show/hide
SecureBanco de dadosMutations, dados sensíveis, DAL
CamadaOnde checar autorização
Server ComponentsRole-based rendering condicional
LayoutsNÃO confiável sozinho, não re-renderiza a cada navegação
Server ActionsTratar como endpoint público, checar sessão + role
Route HandlersTratar 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

  1. Separe Authentication, Session Management e Authorization como três responsabilidades distintas; use bibliotecas de auth prontas para produção real.
  2. Centralize checagens em uma DAL com verifySession() memoizado, chamada em todo data request/Server Action/Route Handler; nunca confie só em Proxy ou Layout.
  3. Proxy é só para checagens otimistas via cookie (nunca DB); é a primeira linha de defesa, não a única.
  4. Use DTOs para retornar apenas os campos necessários, nunca o objeto completo do usuário.
  5. 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.