Capítulo 37 de 456

Data Security

Core Idea

Server Components mudam onde e como dados são acessados; este guia define os três padrões de data fetching recomendados e as práticas de segurança para não vazar dados privados do server para o client.

Key Concepts

  • HTTP APIs (Zero Trust): chame endpoints REST/GraphQL existentes via fetch em Server Components, igual faria em Client Components; bom para apps grandes com equipe de backend separada.
  • Data Access Layer (DAL): módulo interno server-only que centraliza fetch, faz checagem de autorização e retorna DTOs mínimos; recomendado para projetos novos.
  • Component-level data access: query de banco direto no Server Component; rápido para protótipos, mas fácil de vazar campos privados ao passar o objeto inteiro pro Client Component.
  • Tainting: experimental_taintObjectReference e experimental_taintUniqueValue (React) bloqueiam um objeto/valor específico de ser passado ao client; ativado via experimental.taint no next.config.js. É camada extra, não substitui sanitização no DAL.
  • server-only package: import 'server-only' no topo de um módulo causa build error se ele for importado em código client, prevenindo execução client-side de lógica de servidor.
  • Secure action IDs: Next.js gera IDs de Server Action criptografados e não-determinísticos, recalculados periodicamente entre builds (cache de até 14 dias).
  • Dead code elimination: Server Actions não referenciadas em nenhum lugar do app são removidas do bundle client durante next build, não gerando endpoint público.
  • Closures encriptadas: variáveis capturadas por uma Server Action definida dentro de um componente (closure) são enviadas ao client de forma criptografada; chave nova gerada a cada build.
  • NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: env var para fixar a chave de encriptação entre múltiplas instâncias self-hosted (base64, 16/24/32 bytes decodificados).
  • serverActions.allowedOrigins: opção em next.config.js para listar origins confiáveis quando o app roda atrás de proxy reverso, evitando bloqueio por mismatch de Origin/Host.

Code Examples

// data/auth.ts — DAL: helper cacheado por request
import { cache } from 'react'
import { cookies } from 'next/headers'

export const getCurrentUser = cache(async () => {
  const cookieStore = await cookies()
  const token = cookieStore.get('AUTH_TOKEN')
  const decodedToken = await decryptAndValidate(token)
  return new User(decodedToken.id)
})
  • O que demonstra: cache() do React compartilha o mesmo valor entre Server Components na mesma request sem passar prop manualmente, evitando repassar dado sensível adiante.
// app/actions.ts — Server Action reautorizando e checando ownership (IDOR)
'use server'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'

export async function deletePost(postId: string) {
  const session = await auth()
  if (!session?.user) throw new Error('Unauthorized')

  const post = await db.post.findUnique({ where: { id: postId } })
  if (post.authorId !== session.user.id) throw new Error('Forbidden')

  await db.post.delete({ where: { id: postId } })
}
  • O que demonstra: checagem de autenticação (session?.user) e autorização (post.authorId !== session.user.id) dentro da própria action, nunca confiando no check feito na página.

Anti-patterns

  • Confiar em searchParams/inputs do client para decisões de acesso: isAdmin === (await searchParams).isAdmin é falsificável; sempre reverifique via cookies()/sessão no server.
  • Passar objeto de banco inteiro pro Client Component: <Profile user={userData} /> com userData cru expõe todos os campos ao bundle client; retorne apenas os campos necessários.
  • Assumir que auth check da página cobre Server Actions: uma action é um entry point HTTP separado, alcançável por POST direto mesmo sem estar importada; sempre reautentique dentro dela.
  • Retornar o registro completo do banco como resultado da Server Action: valores de retorno são serializados pro client; devolva só o que a UI precisa.
  • Mutação como side-effect de render (ex.: deletar cookie dentro do corpo da Page): Next.js bloqueia isso; use Server Actions.

Key Takeaways

  1. Escolha um único padrão de data fetching (HTTP API, DAL ou component-level) e não misture no mesmo projeto.
  2. Um DAL com server-only + DTOs mínimos é a defesa mais robusta contra vazamento acidental de dado para o client.
  3. Toda Server Action deve reautenticar e reautorizar internamente, independente de checks feitos na página que a renderiza.
  4. Server Actions só aceitam POST e comparam Origin/Host contra CSRF; configure allowedOrigins atrás de proxy.
  5. Em auditoria, foque em: DAL isolado, props de "use client" não vazando dado privado, validação/ownership dentro de "use server", e route.ts/proxy.ts (alto poder, exigem revisão extra).

Connects To

  • Authentication (guide): define como implementar auth()/sessão usado em todo exemplo deste capítulo.
  • Environment Variables (ch041): só o DAL deveria acessar process.env para segredos, conforme recomendado aqui.
  • Forms (ch042): Server Actions usadas em formulários herdam as mesmas exigências de reautorização deste capítulo.