Autenticação B2B Multi-Tenant: Isolamento Absoluto de Dados com Supabase RLS
Índice do Manual
O pesadelo número um de qualquer CEO de um sistema SaaS B2B não é o servidor cair. É o dono da Empresa A fazer login no painel e, por um erro no código, enxergar o faturamento e os dados dos clientes da Empresa B.
Na arquitetura clássica, os desenvolvedores tentam impedir isso colocando comandos WHERE company_id = X espalhados por centenas de arquivos na API Node.js. Se um desenvolvedor júnior esquecer de colocar esse WHERE em apenas uma rota, os dados vazam.
Mas a segurança de isolamento de dados não deve morar no código da aplicação; ela deve morar no núcleo do Banco de Dados. E é exatamente isso que o Supabase Row Level Security (RLS) faz.
Este manual serve como a fundação arquitetural para qualquer SaaS Multi-Tenant orquestrado.
1. O Modelo de Dados (A Fundação)
Para que o isolamento funcione de forma impenetrável, precisamos amarrar o usuário que está logado (Autenticação) a uma Empresa (Tenant).
Abaixo está o script SQL padrão de criação que injetamos no Supabase.
-- 1. Criação da tabela de Locatários (Empresas)
CREATE TABLE companies (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 2. Tabela de Perfis estendendo o auth.users do Supabase
CREATE TABLE profiles (
id UUID REFERENCES auth.users(id) PRIMARY KEY,
company_id UUID REFERENCES companies(id) NOT NULL,
full_name TEXT,
role TEXT DEFAULT 'member'
);
-- 3. Tabela de negócio comum (Ex: Faturas do sistema)
-- Toda tabela do SaaS OBRIGATORIAMENTE precisa da coluna company_id
CREATE TABLE invoices (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
company_id UUID REFERENCES companies(id) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
client_name TEXT NOT NULL
);
Nesta estrutura, o usuário faz o login, o Supabase identifica o ID dele e, através da tabela profiles, nós sabemos exatamente a qual company_id ele pertence.
2. Ativando a Proteção (RLS)
Com as tabelas criadas, o próximo passo é ativar o campo de força. Sem habilitar o RLS, o banco de dados confia em qualquer requisição que venha da sua API (via Service Key). Nós não queremos isso. Nós queremos que o banco de dados confie apenas no Token (JWT) do usuário logado.
-- Ativando o isolamento de segurança nas tabelas
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
A partir desse milissegundo, se a sua API ou Frontend tentar fazer um SELECT * FROM invoices, o banco de dados retornará um vazio total. Ninguém consegue ler ou escrever nada até que nós definamos as regras matemáticas.
3. As Políticas de Segurança Invioláveis (Policies)
Este é o coração da arquitetura. Nós vamos escrever uma regra de banco de dados que diz o seguinte: "Um usuário só pode ler ou inserir uma Fatura se o company_id da fatura for exatamente igual ao company_id do perfil dele".
-- 1. Função de alta performance para pegar a Empresa do usuário logado
-- Usamos uma função para cachear a verificação e não engasgar o banco
CREATE OR REPLACE FUNCTION get_user_company_id()
RETURNS UUID AS $$
SELECT company_id FROM profiles WHERE id = auth.uid();
$$ LANGUAGE sql SECURITY DEFINER;
-- 2. A Política de Segurança da Fatura
CREATE POLICY "Isolamento Total: Usuários só veem faturas da sua empresa"
ON invoices
FOR ALL
USING (
company_id = get_user_company_id()
);
Por que essa arquitetura é perfeita?
Imagine que um hacker tentou manipular a sua API ou que um estagiário na equipe do frontend esqueceu de colocar filtros e fez uma requisição crua querendo ler o faturamento da concorrência: supabase.from('invoices').select('*').
Mesmo que o código esteja errado ou malicioso, a requisição chega no Postgres e o RLS atua como um juiz final:
- "Quem está fazendo essa requisição?" O banco lê o JWT invisível e descobre o ID do usuário.
- "Qual a empresa dele?" A função roda e descobre que ele é da Empresa A.
- "Toma aqui os dados". O banco joga no lixo todas as linhas da Empresa B e da Empresa C, e devolve apenas as linhas da Empresa A.
A segurança é garantida na camada física do armazenamento.
O Padrão Ouro para B2B
Se o seu sistema atende múltiplas empresas e os dados estão separados apenas por filtros fracos em Javascript na camada de aplicação (Node.js), você está a um bug de distância de um processo judicial milionário por vazamento de dados.
O Supabase e o PostgreSQL não são apenas repositórios de armazenamento; eles são motores de lógica militar de segurança. Centralizar o Multi-Tenant no RLS é o que separa sistemas caseiros de plataformas Enterprise.