Next.js sobre Supabase com segurança
Runbook DevOps para Next.js sobre Supabase: arquitetura, middleware, padrões de autenticação, rate limiting e integração com Claude Code.
Depois que o Supabase como plataforma de backend está rodando de forma estável, o verdadeiro stack de aplicação é construído sobre ele.
Em muitos projetos modernos, o Next.js assume o papel da camada de aplicação:
- Frontend Rendering
- Server Side Rendering
- Server Actions
- Route Handlers
- API Proxy
- Session Handling
Com isso, o Next.js se torna efetivamente um gateway de backend entre o navegador e o Supabase.
O erro mais comum é tratar essa camada como “frontend”, quando na verdade ela contém lógica do lado do servidor com altos privilégios.
Este runbook descreve como operar o Next.js com segurança sobre uma plataforma Supabase self-hosted.
Cada seção contém:
- Implementação
- Condição verificável
- Cenário de falha
Resumo - Artigo 2 de 6 da série DevOps Runbook
- Next.js como container separado isolado do Supabase
- Chave service_role em apenas um arquivo
- Middleware com getUser() em cada requisição
- Verificação de ownership antes de mutações
- Rate limiting em endpoints de autenticação
Índice da série
Este guia faz parte da nossa série de runbooks DevOps para stacks de aplicações self-hosted.
- Supabase Self-Hosting Runbook
- Next.js sobre Supabase com segurança - este artigo
- Supabase Edge Functions com segurança
- Trigger.dev Background Jobs em produção segura
- Claude Code como controle de segurança no workflow DevOps
- Security Baseline para todo o stack
O artigo 1 descreve a base da plataforma. Este artigo descreve a camada de aplicação acima dela.
Visão geral da arquitetura
Browser (Client)
|
| HTTPS
|
Next.js App Layer
|
+-- @supabase/ssr
|
+-- service_role client
(apenas contextos admin isolados)
|
Supabase Platform Layer
|
+-- Kong API Gateway
+-- GoTrue Auth
+-- PostgREST API
+-- Realtime
|
PostgreSQL Data Layer
|
+-- Row Level Security
Regras fundamentais:
Browser -> comunica apenas com Next.js
Next.js -> comunica com Supabase
Supabase -> controla acesso via RLS
Quando o navegador se comunica diretamente com vários serviços de backend, surgem fronteiras de segurança incontroláveis.
Parte A - Decisões de arquitetura
Essas decisões raramente são alteradas e formam a base.
A1 - Operar o Next.js como serviço próprio
Implementação
O Next.js roda como seu próprio container.
services
nextjs-app
supabase-stack
postgres
Exemplo docker-compose:
services:
nextjs-app:
build: ./app
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_SUPABASE_URL=http://kong:8000
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
networks:
- internal
O Next.js não pode rodar dentro do stack do Supabase.
Condição verificável
docker ps --format '{{.Names}}'
Resultado esperado:
nextjs-app
supabase-kong
supabase-postgres
supabase-auth
Cenário de falha
Quando o Next.js roda no mesmo container que o Supabase:
- O espaço de processos é compartilhado
- Os secrets ficam no mesmo environment
- Um servidor Next.js comprometido tem acesso direto a todos os serviços de backend
A2 - O navegador comunica apenas com o Next.js
Implementação
O navegador deve ver apenas uma URL pública.
https://app.example.com
Não permitido:
https://app.example.com:8000
https://app.example.com:5432
https://app.example.com:9000
Exemplo de firewall:
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -A INPUT -p tcp --dport 9000 -j DROP
Condição verificável
nmap -p 443,3000,5432,8000,9000 app.example.com
Resultado esperado:
443 open
todos os outros filtered
Cenário de falha
Quando o Supabase Studio está acessível publicamente:
- Acesso completo ao banco de dados
- Manipulação de schema
- Acesso aos Storage Buckets
A3 - Configurar Security Headers
O Next.js funciona como gateway e precisa configurar HTTP Security Headers.
Implementação
next.config.js
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=()" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; img-src 'self' data: blob:; frame-ancestors 'none'"
}
]
Condição verificável
curl -I https://app.example.com
Headers esperados:
X-Frame-Options
Content-Security-Policy
Strict-Transport-Security
Cenário de falha
Sem CSP:
- Ataques XSS carregam scripts externos
- Tokens podem ser exfiltrados
Variáveis de ambiente
| Variável | Visibilidade | Localização permitida | Risco de vazamento |
|---|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Cliente | .env, código cliente | Baixo (URL pública) |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Cliente | .env, código cliente | Médio (limitado pelo RLS) |
| SUPABASE_SERVICE_ROLE_KEY | Apenas servidor | lib/supabase/admin.ts | Crítico (ignora RLS) |
| DATABASE_URL | Apenas servidor | .env (servidor) | Crítico (acesso direto ao DB) |
| TRIGGER_API_KEY | Apenas servidor | .env (servidor) | Alto (acesso à fila de tarefas) |
Parte B - Verificações de implementação
Essas regras se aplicam a cada alteração de código.
B1 - Separar variáveis de ambiente
Visíveis pelo cliente:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Somente servidor:
SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL
TRIGGER_API_KEY
Condição verificável
grep -r "NEXT_PUBLIC_" .env*
Secrets não podem aparecer ali.
Verificação de vazamento no build
grep -r "SERVICE_ROLE" .next/
Resultado esperado:
nenhum resultado
Cenário de falha
service_role no bundle do cliente significa:
- Acesso completo ao banco de dados
- RLS completamente ineficaz
Quem conhece os fundamentos da gestão de secrets entende por que essa separação é essencial.
B2 - Configurar o Supabase SSR Client corretamente
Server Client:
import { createServerClient } from "@supabase/ssr"
O servidor usa a anon key, não a service_role.
Admin Client:
createClient(url, SERVICE_ROLE_KEY)
Apenas para tarefas administrativas.
Condição verificável
grep -rn "SERVICE_ROLE" app/
Resultado esperado: apenas em
lib/supabase/admin.ts
Cenário de falha
Server Client com service_role:
- O RLS é completamente ignorado
- Todos os requests têm privilégios de admin
B3 - Middleware para Auth e Token Refresh
A middleware roda antes de cada request.
middleware.ts
Implementação
const { data: { user } } = await supabase.auth.getUser()
Não:
getSession()
Condição verificável
grep getUser middleware.ts
Cenário de falha
Sem middleware:
- O Token Refresh não funciona
- A sessão expira após 1 hora
B4 - Padrão de mutação
Todas as mutações seguem o mesmo fluxo.
Auth
Input Validation
Ownership Check
Mutation
Logging
Exemplo
const { user } = await supabase.auth.getUser()
if (!user) return
const post = await supabase
.from("posts")
.select("user_id")
.eq("id", postId)
if (post.user_id !== user.id)
throw new Error("forbidden")
Cenário de falha
Sem Ownership Check:
Estatística: De acordo com pesquisas do GitHub de 2024, mais de 23% dos vazamentos de secrets em projetos web envolvem chaves service_role ou tokens de backend equivalentes.
deletePost(id)
Um usuário pode excluir dados de outros.
B5 - Rate Limiting
O Next.js não possui Rate Limiting integrado.
Solução recomendada:
Upstash Redis
Exemplo
10 requests / minute / IP
Condição verificável
for i in {1..20}
do curl -X POST /api/login
done
Resultado esperado:
HTTP 429
B6 - Logging sem secrets
Os logs não podem conter:
JWT Tokens
service_role keys
emails
passwords
Condição verificável
grep console.log app/
Parte C - Operação
C1 - Atualização de dependências
Os releases do Next.js são frequentes.
Verificar semanalmente:
npm audit
npm outdated
Recomendado:
Renovate / Dependabot
C2 - Integração com Claude Code
Arquitetura:
Git Push
|
Checks determinísticos
|
Relatório de segurança
|
Análise do Claude
|
Decisão DevOps
Checks determinísticos
grep service_role
grep NEXT_PUBLIC
npm audit
Análise do Claude
O Claude verifica:
- Novas Server Actions
- Novas API Routes
- Padrões de Ownership
- Input Validation
- Drift de arquitetura
O Claude não executa alterações em produção.
Checklist de deployment
Verificar antes de cada deployment:
[ ] Next.js roda como serviço próprio
[ ] Portas do Supabase fechadas externamente
[ ] Security Headers ativos
[ ] Nenhum service_role no bundle do cliente
[ ] service_role apenas no Admin Client
[ ] middleware.ts presente
[ ] Server Actions verificam Auth
[ ] Ownership Checks implementados
[ ] Rate Limit de login ativo
[ ] npm audit sem achados críticos
Conclusão
O Next.js em um stack moderno não é frontend, mas sim uma camada de servidor privilegiada.
A segurança surge através de três níveis:
Arquitetura
Verificações de implementação
Auditorias contínuas
A combinação de CI Security Checks e análise do Claude Code detecta tanto padrões conhecidos quanto novos riscos.
Quem segue esses princípios junto com uma arquitetura Cert-Ready by Design constrói segurança verificável em vez de auditorias posteriores.
Download da checklist de auditoria
Prompt preparado para o Claude Code. Faça upload do arquivo no seu servidor e inicie o Claude Code no diretório do projeto da sua aplicação Next.js. O Claude Code verificará automaticamente todos os pontos de segurança deste runbook e reportará APROVADO, AVISO ou CRÍTICO.
claude -p "$(cat claude-check-artikel-2-nextjs-br.md)" --allowedTools Read,Grep,Glob,Bash
Baixar checklistÍndice da série
- Supabase Self-Hosting Runbook
- Next.js sobre Supabase com segurança - este artigo
- Supabase Edge Functions com segurança
- Trigger.dev Background Jobs em produção segura
- Claude Code como controle de segurança no workflow DevOps
- Security Baseline para todo o stack
O próximo artigo descreve como Supabase Edge Functions são utilizadas com segurança - sem construir uma segunda arquitetura de backend.

Bert Gogolin
Diretor Executivo, Gosign
AI Governance Briefing
IA empresarial, regulamentação e infraestrutura - uma vez por mês, diretamente de mim.