O que é?
Toda projeto SuperDB tem 2 API keys (ambas são JWTs assinados, ambas vão no header Authorization):
| anon | service-role | |
|---|---|---|
Claim role | anon | service_role |
| Identifica usuário? | Não — só o projeto | Não — só "sou admin" |
| Respeita RLS? | Sim | NÃO — bypassa |
| Pode expor no client? | Sim (env pública) | JAMAIS |
| Permissão | O que RLS deixar | Tudo — qualquer schema, qualquer row |
| Onde fica | NEXT_PUBLIC_SUPERDB_ANON_KEY | SUPERDB_SERVICE_ROLE_KEY (server only) |
A regra é: anon vai no browser, service-role nunca sai do servidor.
Por que importa?
Vazar service-role é a falha de segurança mais comum em apps backend-as-a-service. Cenários reais:
- Dev coloca
SUPERDB_SERVICE_ROLE_KEYnumNEXT_PUBLIC_*achando que é igual à anon — Next bundle expõe no JS do browser → todo mundo no mundo pode ler/escrever qualquer tabela. - Service-role commitado num
.envque foi pro Git público. - Service-role num app mobile compilado — quem decompila tem o app inteiro.
Anon, por outro lado, é desenhada pra ser pública. Ela diz "sou um cliente do projeto X" — e nada mais. Sem RLS por trás, ela não permite ler/escrever nada útil.
Se você vazou a service-role: vá AGORA no Dashboard → Settings → API → "Rotate service-role key". Todos os tokens antigos são invalidados imediatamente. Atualize .env dos seus servidores.
Como funciona no SuperDB
As duas keys são JWTs HS256 assinados com o mesmo JWT secret do projeto. O que muda é o role no payload:
{
"iss": "superdb",
"ref": "acme-prod",
"role": "anon",
"iat": 1714000000,
"exp": 2030000000
}
{
"iss": "superdb",
"ref": "acme-prod",
"role": "service_role",
"iat": 1714000000,
"exp": 2030000000
}
No Postgres, as duas roles existem como usuários reais:
CREATE ROLE anon NOINHERIT;
CREATE ROLE authenticated NOINHERIT;
CREATE ROLE service_role NOINHERIT BYPASSRLS; -- ← essa
O atributo BYPASSRLS é o que faz a service-role ignorar todas as policies. Quando PostgREST faz SET ROLE service_role, RLS para de funcionar pra aquela transação.
Quando usar (e quando não)
Use anon em…
- Código do client — browser, mobile, app desktop
- Server Components que rodam por usuário — Next.js middleware que valida o JWT do user e faz queries em nome dele
- Edge functions com identidade do user — Cloudflare Workers que recebem o JWT e fazem proxy
Use service-role em…
- Admin scripts — seed data, migrations programáticas
- Cron jobs — limpeza, agregações, relatórios noturnos
- Webhooks de terceiros — Stripe/billing chega sem JWT de user; você precisa criar registros em nome do sistema
- Background workers — fila que processa eventos sem usuário associado
- Importação em batch — copiar 10M rows de um CSV legado
NUNCA use…
- Service-role no client — vaza o projeto inteiro
- Anon no server pra fazer admin — anon respeita RLS; sem JWT de user, retorna vazio na maior parte das queries (o que parece "não funcionar")
Exemplos práticos
1. Client com anon (padrão)
import { createClient } from '@superdb/supabase-compat'
export const db = createClient(
process.env.NEXT_PUBLIC_SUPERDB_URL!,
process.env.NEXT_PUBLIC_SUPERDB_ANON_KEY!
)
// Quando o user loga, o SDK guarda o JWT dele em cookie
// e envia tanto a anon key (no header `apikey`) quanto o JWT
// do user (no header `Authorization`).
// RLS então roda com auth.uid() = user real.
2. Server Action com service-role pra admin op
'use server'
import { createClient } from '@superdb/supabase-compat'
// SUPERDB_SERVICE_ROLE_KEY: sem NEXT_PUBLIC_ ⇒ só server
const admin = createClient(
process.env.SUPERDB_URL!,
process.env.SUPERDB_SERVICE_ROLE_KEY!,
{ auth: { persistSession: false } }
)
export async function banUser(userId: string) {
// Antes: cheque autorização EXPLICITAMENTE (você bypassou RLS!)
const session = await getServerSession()
if (session.user.role !== 'admin') throw new Error('forbidden')
await admin.from('users').update({ banned: true }).eq('id', userId)
}
Sempre que você usar service-role: ANTES da query, valide manualmente que o caller tem permissão. RLS não vai te salvar — você desligou ela.
3. MCP server usa service-role (intencional, roda local)
{
"mcpServers": {
"superdb": {
"command": "npx",
"args": ["@superdb/mcp"],
"env": {
"SUPERDB_URL": "https://api.superdb.com.br",
"SUPERDB_SERVICE_ROLE_KEY": "eyJhbGc..."
}
}
}
}
Tudo bem porque o MCP server roda no seu computador, não no client de ninguém. Mesma analogia de psql com senha do banco.
Armadilhas comuns
- Commitar service-role no Git. Mesmo num repo privado, rotacione já. GitHub scanners + leaks acontecem.
- Prefixar service-role com
NEXT_PUBLIC_. Next.js inclui qualquerNEXT_PUBLIC_*no bundle JS. Erro fatal — rotacione. - Usar service-role no client achando que "RLS protege". Não protege — service-role bypassa RLS por design.
- Usar anon no server pra coisa admin. "Por que minha query retorna vazio?" — porque anon respeita RLS e o server não tem JWT de usuário válido. Use service-role.
- Não rotacionar quando dev sai do time. Saiu da empresa com acesso? Rotacione service-role + JWT secret. Anon pode ficar (era pública mesmo).
- Confundir
apikeyheader comAuthorization.apikeyé a API key (anon ou service).Authorizationé o JWT do user. SDK envia os dois. - Service-role em logs. Acidente comum em
console.log(env). Adicione filtro no logger.
Best practice: tenha uma env var SUPERDB_SERVICE_ROLE_KEY SÓ no servidor (Vercel server-side env, Coolify secrets, AWS Secrets Manager). Nunca prefixe com NEXT_PUBLIC_, VITE_, REACT_APP_.
Termos relacionados
- JWT — ambas as keys são JWTs
- RLS — o que service-role bypassa
- JWT hook — pra customizar claims além de role
- Glossário: service-role