Scopes OAuth são design de autorização, não configuração de checkbox
Scopes OAuth aparecem na tela de consentimento, mas o trabalho real deles é codificar o modelo de autorização que clientes de terceiros herdam. Scopes desenhados como configuração de checkbox — amplos, vagos, nunca validados no servidor — transformam cada integração em acesso super-privilegiado esperando um token roubado.
Uma integração de terceiros solicita acesso read e write. O usuário clica em Permitir. Seis meses depois, um refresh token vazado concede mutação completa da conta porque ninguém definiu o que write significava no resource server — e ninguém verifica scopes em endpoints individuais de qualquer forma. A autorização com scopes OAuth não é um checkbox de UX na tela de consentimento. É o contrato entre provedor de identidade, cliente e resource server sobre o que um bearer token pode fazer. Quando scopes são desenhados como rótulos improvisados em vez de permissões executáveis, cada integração herda a lacuna entre o que usuários acham ter aprovado e o que o código realmente permite.
OAuth 2.1 consolidou a linha de base de segurança: PKCE obrigatório para clientes públicos, access tokens de curta duração, rotação de refresh tokens e desencorajamento explícito do fluxo implícito. Scopes permanecem o vocabulário de autorização — mas vocabulário sem gramática é ruído. A gramática é a aplicação no resource server: cada endpoint protegido valida o claim scope do token contra a operação que executa, retorna 403 com insufficient_scope quando o claim falta, e nunca infere permissão por correspondências parciais de strings.
Scopes falham quando descrevem clientes em vez de capacidades
O erro de design de scopes mais comum mapeia permissões para aplicações integradoras em vez de recursos e ações. calendar-app:full-access não diz nada ao resource server sobre quais operações de calendário são permitidas. google-calendar como scope único não diz nada ao usuário sobre o que o app fará de fato.
O padrão durável é recurso:ação — às vezes estendido para recurso:subrecurso:ação quando a superfície do produto justifica:
| Scope | Permite | Não permite |
|---|---|---|
invoices:read | GET /invoices, GET /invoices/:id | Criar, atualizar, excluir |
invoices:write | Criar e atualizar faturas | Excluir (scope separado) |
invoices:delete | DELETE /invoices/:id | Exportação somente leitura |
profile:read | Ler campos do perfil do usuário | Envio de email, troca de senha |
Hierarquia pode simplificar consentimento sem inflar a aplicação. Um scope pai invoices:manage pode implicar invoices:read e invoices:write no authorization server — mas o resource server deve verificar o scope canônico da operação, não assumir hierarquia a menos que configurada explicitamente. Hierarquia implícita que existe apenas na documentação falha na primeira vez que um cliente solicita o scope pai e o servidor concede acesso a endpoints de exclusão que o time de produto nunca pretendia.
Evitar scopes catch-all. admin, full_access e identity.full existem porque são fáceis de solicitar e difíceis de revisar. Derrotam o propósito de tokens com scope: limitar o blast radius quando um token vaza. Um token comprometido com invoices:read expõe dados de faturas. Um token comprometido com admin expõe todo o tenant.
Scopes são limites de menor privilégio, não rótulos de marketing na tela de consentimento.
Agentes de IA e clientes de automação intensificam o problema. Um agente com email.send e uma lista de destinatários alucinada pode danificar a confiança mais rápido que um humano com o mesmo scope — porque o agente age em velocidade de máquina sem fricção social. Scopes granulares para clientes agente (email:send:draft-only, calendar:read:next-7-days) alinham capacidade à tarefa em vez de conceder acesso amplo perpétuo no momento do consentimento.
Resource servers devem validar scopes em cada requisição
Scopes acordados no momento da autorização não significam nada se o API gateway e os handlers de recursos não os aplicam. A validação pertence a duas camadas.
Camada gateway — assinatura JWT, iss, aud, exp, nbf, e presença grosseira de scope para prefixos de rota. Rejeitar tokens malformados ou expirados antes do tráfego chegar ao código da aplicação. Validação local de JWT evita round trips por requisição ao provedor de identidade — o padrão dominante em 2026 para APIs sensíveis à latência.
Camada handler — verificação de scope específica da operação dentro da lógica de negócio que sabe o que o endpoint faz. DELETE /invoices/:id requer invoices:delete, não meramente invoices:write. A verificação usa pertencimento exato ao conjunto após dividir a string de scopes delimitada por espaços — nunca correspondência por substring, que produz falsos positivos quando read corresponde a read_everything.
function requireScopes(token: AccessToken, required: string[]): void {
const granted = new Set(token.scope.split(" "))
const missing = required.filter((s) => !granted.has(s))
if (missing.length > 0) {
throw new InsufficientScopeError(missing)
}
}
// Route handler
app.delete("/invoices/:id", async (req, res) => {
requireScopes(req.token, ["invoices:delete"])
await invoiceService.delete(req.params.id)
res.status(204).end()
})Retornar 403 Forbidden com WWW-Authenticate: Bearer error="insufficient_scope", scope="invoices:delete" — não um 401 genérico que confunde falha de autenticação com falha de autorização. Clientes e operadores diagnosticam scopes faltantes a partir da resposta sem adivinhar.
Clientes machine-to-machine usam o fluxo client credentials com scopes restritos à identidade de serviço — nunca permissões delegadas de usuário. Um worker de faturamento noturno precisa de invoices:read e billing:aggregate:write, não de um superconjunto herdado do token de um admin humano. Os secrets de CI/CD que alimentam pipelines frequentemente carregam essas credenciais; design de scopes e rotação de secrets são a mesma conversa de segurança.
Telas de consentimento refletem a qualidade do design de scopes
Usuários experimentam scopes no momento do consentimento. Design pobre produz fadiga de consentimento — doze permissões granulares que soam iguais — ou cegueira de consentimento — uma permissão full_access que usuários aprovam sem ler porque não significa nada.
O equilíbrio para OAuth voltado ao usuário:
- Agrupar por área de produto na tela de consentimento: Faturamento, Projetos, Perfil — cada uma com variantes read/write conforme necessário.
- Explicar consequências em linguagem clara vinculada a nomes de scope:
invoices:delete→ "Excluir faturas permanentemente." - Solicitar incrementalmente quando possível — registro dinâmico de clientes e consentimento escalonado para scopes sensíveis em vez de pedir tudo no momento da instalação.
Desenvolvedores de terceiros sempre solicitarão os scopes mais amplos que o authorization server permitir. O trabalho do servidor é conceder menos do que solicitado quando o cliente não precisa de acesso completo — uma característica de authorization servers bem configurados que muitos times desabilitam por conveniência.
Para clientes first-party (app da mesma organização acessando API da mesma organização), consentimento costuma ser omitido — mas a aplicação de scopes no resource server deve executar igual. Tokens first-party vazam por logs, armazenamento do navegador e comprometimentos de supply chain da mesma forma que tokens de terceiros.
Como os scopes OAuth devem codificar decisões de autorização?
Essas perguntas expõem as lacunas entre a configuração do provedor de identidade e a aplicação na API.
Os scopes devem ser granulares ou amplos?
Granulares para clientes machine e agentes de IA onde menor privilégio limita o blast radius automatizado. Moderadamente amplos para consentimento voltado ao usuário onde projects:read e projects:write são compreensíveis sem vinte sub-scopes. A regra: cada scope mapeia para uma verificação executável em um endpoint ou classe de recurso específica — se a aplicação não consegue articular a verificação, o scope é vago demais.
Onde acontece a validação de scopes?
No API gateway para validade do token e guardas grosseiras de rota. No handler para verificações específicas de operação. Nunca apenas no authorization server durante a emissão do token — scopes emitidos são claims, não aplicação. Tokens vivem minutos a horas após a emissão; revogação e durações curtas ajudam, mas verificações em handlers são o portão autoritativo.
Como scopes interagem com RLS e APIs internas?
Scopes OAuth governam o que uma aplicação cliente pode solicitar. Row-Level Security no banco de dados governa quais linhas uma identidade de usuário específica pode ver dentro de uma requisição permitida. Ambas as camadas devem se sustentar: um token com invoices:read ainda retorna apenas as faturas que o usuário autenticado possui sob RLS. Scopes não substituem autorização na camada de dados — são a verificação de perímetro antes da query rodar. O guia de hardening de segurança no Supabase cobre a camada de banco de dados; scopes cobrem o perímetro da API.
Um argumento comum vai na direção oposta
A postura contrária sustenta que scopes OAuth são complexidade legada — que API keys com allowlists de IP, mutual TLS ou motores de política centralizados como Cedar substituem strings de scope por regras baseadas em atributos mais ricas.
Motores de política agregam valor para delegação complexa — cadeias multi-agente, condições de atributos, ABAC dinâmico. Não substituem scopes para integrações OAuth padrão de terceiros onde clientes, telas de consentimento e claims de token devem falar um vocabulário portável. A arquitetura produtiva usa scopes como formato de claim estável e motores de política para service mesh interno ou orquestração de agentes onde strings de scope sozinhas são insuficientes.
Scopes não são a forma final de autorização. São a linha de base interoperável que resource servers podem aplicar hoje sem que cada cliente implemente uma linguagem de políticas.
Pontos-chave
- Scopes OAuth codificam o modelo de autorização — padrões recurso:ação, não rótulos vagos de cliente.
- Resource servers validam scopes por endpoint com pertencimento exato ao conjunto — nunca correspondência por substring.
- Scopes catch-all (
admin,full_access) derrotam menor privilégio e amplificam o impacto do roubo de tokens. - Gateway valida claims do token; handlers validam scopes específicos de operação.
- Clientes de IA e M2M precisam de scopes mais finos do que consentimento voltado ao usuário costuma mostrar.
- Scopes governam o perímetro da API; RLS e políticas internas governam acesso a dados dentro de requisições permitidas.
Conclusão
A tela de consentimento é onde usuários veem scopes. O resource server é onde scopes importam. Times que tratam design de scopes como configuração de checkbox entregam integrações rápido e passam trimestres limpando depois que o primeiro token vazado prova que write significava tudo.
O exercício de design é curto: listar cada operação protegida, atribuir um scope, implementar a verificação no handler, e verificar que o texto da tela de consentimento corresponde ao que a verificação aplica. Lacunas entre esses quatro artefatos são bugs de autorização esperando produção — não achados de segurança esperando um penetration test.


