Pular para o conteúdo
🧭 CONCEITOS

Row Level Security — o segurança dentro do banco.

RLS é uma feature do Postgres que aplica filtros automáticos a queries baseado no usuário autenticado. O SuperDB usa RLS pra que SELECT * FROM tasks retorne só o que o usuário pode ver — sem precisar adicionar WHERE user_id = ? em todo lugar.

O que é?

Row Level Security (RLS) é uma feature nativa do Postgres desde a versão 9.5 (2016). Ela permite escrever políticas SQL que dizem quais linhas (rows) um usuário pode ver, criar, atualizar ou deletar — e o banco aplica isso automaticamente em toda query.

Sem RLS, é sua aplicação que filtra:

sem RLS (no código)
// Você precisa lembrar de filtrar em CADA query
const { data } = await db
  .from('tasks')
  .select('*')
  .eq('user_id', currentUser.id)  // ← esqueceu? vaza tudo.

Com RLS, o banco filtra:

com RLS (no banco)
// O SELECT é "puro" — RLS adiciona o filtro
const { data } = await db.from('tasks').select('*')
// Postgres traduz pra: SELECT * FROM tasks WHERE user_id = auth.uid()

A política vive no banco. Toda forma de acesso — SDK, API REST, SQL direto, Studio — passa pela mesma regra.

Por que importa?

  • Centraliza a regra. Mudança de permissão é uma policy, não vasculhar 87 endpoints.
  • Defesa em profundidade. Mesmo se um endpoint tem bug e esqueceu de filtrar, RLS pega.
  • Funciona pra REST e SQL. Sua API REST, suas funções RPC, suas migrations — todas respeitam.
  • Auditoria mais simples. Mostra as policys pra auditor, ele entende o modelo de permissão num arquivo.
💡

Princípio: "trust no client". O cliente envia o JWT, mas é o servidor que decide o que cada JWT pode ler. RLS é onde essa decisão acontece.

Como funciona no SuperDB

O fluxo, do request ao banco:

  1. Cliente envia request com Authorization: Bearer <jwt>
  2. PostgREST (ou o serviço da API) valida a assinatura do JWT
  3. Antes de executar a query, PostgREST seta variáveis de sessão:
    • SET LOCAL request.jwt.claims = '{...}'
    • SET LOCAL role = 'authenticated' (ou anon)
  4. A função auth.uid() retorna request.jwt.claims->>'sub'
  5. Postgres adiciona o filtro das policies à query

Funções helper que o SuperDB instala no schema auth:

auth — built-in functions
auth.uid()    -- UUID do usuário (do claim sub)
auth.role()   -- 'authenticated' | 'anon' | 'service_role'
auth.email()  -- email do usuário (do claim email)
auth.jwt()    -- JSON completo dos claims

Ativar RLS numa tabela

RLS não é ativa por padrão. Toda tabela nova tem que ser explicitamente protegida:

migration.sql
CREATE TABLE tasks (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id uuid REFERENCES auth.users NOT NULL,
  title text NOT NULL,
  done boolean DEFAULT false
);

-- LIGAR RLS (sem isso, a policy abaixo é ignorada)
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Criar a policy
CREATE POLICY "own tasks" ON tasks
  FOR ALL                        -- aplica em SELECT/INSERT/UPDATE/DELETE
  USING (user_id = auth.uid())   -- linhas que o user PODE LER/UPDATE/DELETE
  WITH CHECK (user_id = auth.uid()); -- linhas que ele pode INSERT/UPDATE pra
⚠️

USING vs WITH CHECK: USING filtra o que ele . WITH CHECK filtra o que ele cria/altera. Faltando WITH CHECK, um user pode criar uma row com user_id de outro user (e depois não vê — mas o outro vê).

Quando usar (e quando não)

Use SEMPRE em…

  • Tabelas com dados de usuário. tasks, messages, orders, posts — tudo que tem "dono"
  • Tabelas de relacionamento. team_members, shared_with — onde permissão depende de outras tabelas
  • Dados sensíveis. Mesmo "só admins vêem" — escreva a policy

Pode pular em…

  • Tabelas de config global. app_settings, country_codes, feature_flags — leitura pública, escrita só por admin (use GRANT básico)
  • Views read-only públicas. Onde RLS da tabela base já cobre
ℹ️

Mesmo "tabela só leitura pra todo mundo": o SuperDB recomenda ENABLE ROW LEVEL SECURITY + POLICY USING (true). Explícito é melhor que implícito — e o Studio mostra um warning vermelho em tabelas sem RLS.

Exemplos práticos

1. Own — só vê o que é seu

policy: own rows
CREATE POLICY "users see own tasks"
  ON tasks FOR ALL
  USING (user_id = auth.uid())
  WITH CHECK (user_id = auth.uid());

2. Team — todos do time vêem

policy: team rows
CREATE POLICY "team sees team tasks"
  ON tasks FOR SELECT
  USING (
    team_id IN (
      SELECT team_id FROM team_members WHERE user_id = auth.uid()
    )
  );

CREATE POLICY "team writes own"
  ON tasks FOR INSERT
  WITH CHECK (
    team_id IN (
      SELECT team_id FROM team_members WHERE user_id = auth.uid()
    )
  );

3. Public-read, owner-write — blog clássico

policy: public read + owner write
-- Todo mundo (logado ou não) lê posts publicados
CREATE POLICY "public reads published"
  ON posts FOR SELECT
  USING (published = true);

-- Mas só o autor cria/edita/apaga
CREATE POLICY "author writes own"
  ON posts FOR INSERT WITH CHECK (author_id = auth.uid());
CREATE POLICY "author updates own"
  ON posts FOR UPDATE USING (author_id = auth.uid());
CREATE POLICY "author deletes own"
  ON posts FOR DELETE USING (author_id = auth.uid());

Armadilhas comuns

  • Esqueceu ENABLE ROW LEVEL SECURITY. Tabela sem RLS ativa ignora todas as policies — retorna tudo. Sempre cheque SELECT relrowsecurity FROM pg_class WHERE relname = 'tasks';.
  • Policy sem WITH CHECK em INSERT. Permite criar rows com user_id de outro user. Sempre escreva os dois quando faz FOR ALL.
  • Service-role bypassa RLS. Por design — admin script pode tudo. Mas se você usa service-role no client achando que "RLS protege", está errado. Veja service vs anon.
  • Policies são OR, não AND. Se você tem 2 policies SELECT, a row aparece se qualquer uma liberar. Pra restringir, use FOR ... USING (a AND b) em uma policy só.
  • Performance: RLS adiciona um WHERE ao plano. Garante que as colunas usadas em USING estão indexadas — sem índice em user_id, query lenta.
  • Subqueries em policies podem ser caras. Policy que faz SELECT FROM teams WHERE... em toda row é lenta com muitas rows. Considere materializar permissão.
  • RLS não cobre coluna. RLS é por linha. Pra esconder colunas (ex: credit_card), use views ou REVOKE coluna.
🚨

Anti-padrão: nunca confie em "o frontend nunca pediria essa row". Frontend pode ser modificado, request pode ser feito direto com cURL. RLS é o garantidor — não o frontend.

Termos relacionados

Essa página ajudou?