
Autenticação vs. autorização: a diferença que eu confundia no começo
Todo sistema tem as duas. A maioria dos bugs de segurança que vi em código júnior era confundir uma com a outra — ou implementar autorização onde precisava de autenticação.
Autenticação e autorização parecem a mesma coisa quando você começa. Não são.
Confundir as duas é a origem de uma classe inteira de bugs de segurança — bugs que não aparecem em teste local mas explodem em produção com dois usuários reais.
A diferença em uma linha
Autenticação: quem é você?
Autorização: o que você pode fazer?
Autenticação → "Esse token é de João"
Autorização → "João pode acessar /admin/relatorios?"
Autenticação acontece primeiro. Autorização usa o resultado da autenticação para tomar decisão.
O fluxo completo
sequenceDiagram
participant C as Cliente
participant A as API
participant DB as Banco
C->>A: POST /login (email+senha)
A->>DB: verifica credenciais
DB-->>A: usuário encontrado + roles
A-->>C: JWT token (sub=user_id, roles=[admin])
C->>A: GET /relatorios (Authorization: Bearer <jwt>)
A->>A: valida JWT (autenticação)
A->>A: verifica role=admin (autorização)
A->>DB: busca relatórios
DB-->>A: dados
A-->>C: 200 + dados
Dois checks separados. Dois motivos diferentes para retornar 401 vs 403.
401 vs 403: o que o status code diz
| Status | Significa | O cliente deve | |--------|-----------|----------------| | 401 Unauthorized | Não autenticado | Fazer login | | 403 Forbidden | Autenticado mas sem permissão | Não tentar de novo (ou pedir permissão) |
Bug clássico: retornar 403 quando deveria ser 401. O cliente tenta renovar permissão em vez de renovar token. Loop de confusão.
Onde vi o bug mais comum
Um sistema que tinha endpoint /api/dados-sensiveis.
O middleware de autenticação validava o token corretamente. Mas a verificação de permissão dentro do endpoint era:
# Bug: verifica se tem campo "admin" no body da request
# não verifica o role do usuário autenticado
if request.json.get('admin') == True:
return dados_sensiveis()
Qualquer um autenticado podia mandar {"admin": true} no body e acessar. A autenticação funcionava. A autorização estava no lugar errado.
Correto:
# Correto: lê do token JWT, não do body
if current_user.role != 'admin':
return jsonify({'error': 'forbidden'}), 403
return dados_sensiveis()
O corpo da requisição vem do cliente e não é confiável. O token JWT é assinado pelo servidor e é confiável (dentro da validade).
O que aprendi sobre JWT que não estava no tutorial
JWT não é sessão. Não tem revogação por padrão.
Se você emite um JWT com 24h de validade e o usuário faz logout, o token ainda é válido por 24h. A menos que você implemente lista de revogação no servidor — o que elimina parte da vantagem do JWT (stateless).
Para a maioria dos casos de uso, a solução prática é: token de curta duração (15min–1h) + refresh token com revogação. O access token expira rápido. O refresh token fica no banco e pode ser revogado no logout.
Não aprendi isso no começo. Aprendi quando precisei invalidar token de usuário que reportou acesso indevido e não sabia como.
A pergunta que faz diferença
Antes de implementar qualquer verificação de acesso:
"O que estou checando aqui — se o usuário está logado, ou se o usuário tem permissão para esta ação?"
Se for "está logado": autenticação. Verifica o token.
Se for "tem permissão": autorização. Verifica role/escopo/ownership.
São implementações diferentes. São erros de segurança diferentes quando feitas errado.
Essa semana: abre um endpoint do seu projeto que protege dados sensíveis. Rastreia: onde é feita autenticação? Onde é feita autorização? São lugares diferentes no código? Se for o mesmo lugar, pode ser que estejam misturados de um jeito que vai causar problema.