Autenticação vs Autorização: a diferença que quebra APIs em produção
← Voltar para Codeshort

Autenticação vs Autorização: a diferença que quebra APIs em produção

JWT valida quem você é. Não diz o que você pode fazer. Entenda a diferença antes que ela vire um bug de segurança.

DC
Dev Code Software
07 de agosto de 2026·7 min de leitura

Um bug que não é bug, é modelo mental errado

Usuário loga normalmente, token válido, tudo certo. Aí ele edita o id na URL de /api/orders/482 para /api/orders/483 e vê o pedido de outro cliente.

Isso não é falha de autenticação. O sistema sabe exatamente quem é aquele usuário. O problema é que ninguém checou se ele podia ver aquele pedido. Autenticação e autorização resolvem perguntas diferentes, e confundir as duas é uma das causas mais comuns de vulnerabilidade em API que vejo em code review. Vamos separar isso de vez.

O que é autenticação, de verdade

Autenticação responde uma pergunta só: quem é você?

É o processo de provar identidade. Login com senha, token JWT, OAuth, biometria, chave de API — todos são mecanismos de autenticação. O resultado de uma autenticação bem-sucedida é uma identidade confirmada, normalmente representada por um objeto user ou um sub dentro de um token.

// Autenticação: confirma QUEM é o usuário
const jwt = require('jsonwebtoken');

function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Token ausente' });
  }

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    req.user = payload; // { id: 42, email: 'dev@empresa.com', role: 'editor' }
    next();
  } catch {
    return res.status(401).json({ error: 'Token inválido' });
  }
}

Repare que esse middleware não decide nada sobre permissões. Ele só garante que req.user existe e é confiável. Ponto final.

O que é autorização, de verdade

Autorização responde outra pergunta: o que você pode fazer?

Ela roda depois da autenticação e decide se aquela identidade já confirmada tem permissão para a ação específica que está tentando executar. Editar um post, deletar um usuário, ver o pedido 483 — cada uma dessas ações passa por uma checagem de autorização separada.

// Autorização: decide O QUE o usuário pode fazer
function authorize(requiredRole) {
  return (req, res, next) => {
    if (req.user.role !== requiredRole) {
      return res.status(403).json({ error: 'Permissão insuficiente' });
    }
    next();
  };
}

app.delete('/api/users/:id', authenticate, authorize('admin'), deleteUser);

Por que separar em dois middlewares? Porque são responsabilidades diferentes, com falhas diferentes. Uma falha de autenticação te dá 401 Unauthorized. Uma falha de autorização te dá 403 Forbidden. Se seu sistema devolve o mesmo código pros dois casos, isso já é um sinal de que o modelo mental está misturado.

💡 Dica: se você está debugando um "erro de permissão" e o log mostra 401, o problema é autenticação, não autorização. Muita gente perde tempo procurando regra de permissão no lugar errado porque não olhou o status code certo.

Onde as duas se cruzam no código

Na prática, autenticação e autorização formam uma cadeia. Primeiro você confirma a identidade, depois checa a permissão pra aquela ação específica. A ordem importa: nunca dá pra autorizar alguém que você ainda não autenticou.

// A ordem é sempre: autentica -> autoriza -> executa
app.put(
  '/api/orders/:id/refund',
  authenticate,              // 1. Quem é você?
  authorize('finance'),      // 2. Você pode reembolsar pedidos?
  checkOwnership,            // 3. Esse pedido é seu ou da sua equipe?
  refundOrder                // 4. Executa a ação
);

Isso aparece de outra forma também: um usuário autenticado como role: 'editor' não precisa de novo login pra cada ação. A sessão dele já está confirmada. O que muda de uma rota pra outra é só a regra de autorização aplicada em cima dessa mesma identidade.

Exemplo real: JWT + RBAC numa API Express

Isso apareceu num PR meu em 2022 e o revisor só comentou: "por quê?". Boa pergunta. Eu tinha colocado a checagem de role dentro do controller, misturada com a lógica de negócio, porque "ia mais rápido assim". Resultado: três rotas diferentes reimplementando a mesma checagem de forma levemente diferente, e uma delas esqueceu de validar o role de admin em produção por duas semanas.

A correção foi separar autorização em uma camada própria, reutilizável, testável isoladamente:

// ❌ Autorização misturada com lógica de negócio
app.delete('/api/posts/:id', authenticate, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (req.user.role !== 'admin' && req.user.id !== post.authorId) {
    return res.status(403).json({ error: 'Sem permissão' });
  }
  await post.remove();
  res.sendStatus(204);
});

// ✓ Autorização como camada isolada e reutilizável
function canDeletePost(user, post) {
  return user.role === 'admin' || user.id === post.authorId;
}

app.delete('/api/posts/:id', authenticate, async (req, res) => {
  const post = await Post.findById(req.params.id);

  if (!canDeletePost(req.user, post)) {
    return res.status(403).json({ error: 'Sem permissão' });
  }

  await post.remove();
  res.sendStatus(204);
});

A diferença parece pequena, mas canDeletePost agora pode ser testada com um teste unitário simples, sem subir servidor, sem mockar request. E se amanhã a regra virar "editores da mesma equipe também podem deletar", você muda em um lugar só.

Erros comuns que devs cometem

Verificar role no frontend e confiar nisso. Esconder um botão de "deletar" no React não é autorização, é UX. A API tem que recusar a chamada mesmo que o botão nunca devesse aparecer.

Colocar tudo dentro do payload do JWT e confiar cegamente nele. Token com role: 'admin' assinado há seis meses continua válido mesmo que o usuário tenha sido rebaixado ontem. Se sua aplicação tem mudança de permissão frequente, considere token de vida curta ou checagem de role direto no banco em ações sensíveis.

Confundir "autenticado" com "autorizado para tudo". Isso é o erro do exemplo do início do artigo: o middleware de autenticação passa, e daí a rota assume que está tudo liberado. Todo endpoint que expõe dado por id precisa de uma checagem de ownership, não só de token válido.

Reaproveitar a mesma mensagem de erro para 401 e 403. Isso dificulta debug para seu próprio time e também vaza menos informação pra um atacante tentando mapear rotas — só que na direção errada: você quer que seu time debugue rápido e o atacante não descubra nada, e mensagem genérica atrapalha os dois objetivos ao mesmo tempo.

⚠️ Atenção: IDOR (Insecure Direct Object Reference) — quando um usuário autenticado acessa recurso de outro só trocando um ID na URL — é uma das vulnerabilidades mais comuns em APIs REST. Ela nasce exatamente dessa confusão entre autenticação e autorização.

Tabela comparativa

AspectoAutenticaçãoAutorização
Pergunta que respondeQuem é você?O que você pode fazer?
Quando rodaAntesDepois da autenticação
Status HTTP de falha401 Unauthorized403 Forbidden
ExemplosLogin, JWT, OAuth, biometriaRBAC, ABAC, checagem de ownership
Onde costuma quebrarToken expirado, senha erradaFalta de checagem por recurso

FAQ

401 e 403 são a mesma coisa? Não. 401 significa que a identidade não foi confirmada — token ausente, expirado ou inválido. 403 significa que a identidade foi confirmada, mas ela não tem permissão para aquela ação específica. Devolver o código certo economiza tempo de debug pro seu próprio time.

OAuth é autenticação ou autorização? As duas coisas, dependendo da camada. OAuth 2.0 puro é um protocolo de autorização — ele emite tokens de acesso a recursos. O OpenID Connect, construído em cima do OAuth 2.0, adiciona a camada de autenticação (o id_token). É por isso que "login com Google" mistura os dois conceitos no fluxo.

Preciso de uma biblioteca de autorização ou dá pra fazer na mão? Para RBAC simples (poucos papéis fixos, tipo admin/editor/viewer), uma função isolada como canDeletePost já resolve. Quando as regras crescem — permissões por recurso, por equipe, por contexto — vale considerar algo como Casbin, CASL ou OPA antes que a lógica vire spaghetti espalhado pelos controllers.

JWT sem estado permite revogar permissão em tempo real? Não por padrão. Um token assinado continua válido até expirar, mesmo que a role do usuário mude no banco. Para ações sensíveis, ou reduza o tempo de expiração do token, ou valide a role atual direto no banco em vez de confiar só no payload.

Middleware de autorização deve ficar no controller ou em camada separada? Camada separada, sempre que a regra puder ser reaproveitada em mais de uma rota. Isso facilita teste unitário e evita que duas rotas implementem a mesma regra de formas ligeiramente diferentes — que foi exatamente o problema do PR que mencionei antes.

Próximos passos

Se você quer aplicar isso ainda hoje no seu projeto:

  1. Audite as rotas que recebem id na URL ou no body e confirme se cada uma tem checagem de ownership, não só de token.
  2. Separe autorização em funções isoladas e testáveis, fora do controller.
  3. Confirme que sua API devolve 401 para falha de identidade e 403 para falha de permissão — nunca o mesmo código para os dois.
  4. Se o app tem mudança de permissão frequente, revise o tempo de expiração dos tokens antes que isso vire incidente.

Autenticação garante que você sabe quem está do outro lado. Autorização garante que essa pessoa só toca no que deveria. Os dois juntos formam a base de qualquer API que lida com dado sensível — e tratados como a mesma coisa, formam a base de boa parte dos incidentes de segurança que a gente lê em post-mortem.