Segurança e identidade

OAuth 2.0 explicado: papéis, grants, PKCE e boas práticas atuais

Intermediário8 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

O OAuth 2.0 permite que uma aplicação aja em nome de um usuário sem nunca ver a senha dele, e quase todo erro com ele vem de não entender direito o que ele delega. Este guia percorre os papéis, as mensagens HTTP do authorization code flow com PKCE, os outros grants que vale a pena conhecer, como funcionam scopes e consentimento, e o que as boas práticas de segurança atuais removem da especificação original.

O que você vai aprender

  • Explicar o que o OAuth 2.0 delega e por que ele trata de autorização, e não de autenticação
  • Acompanhar o authorization code flow com PKCE mensagem por mensagem
  • Escolher o grant certo para uma aplicação de navegador, um app mobile, um job de backend ou uma TV
  • Usar corretamente scopes, consentimento, refresh tokens, introspecção e revogação
  • Aplicar as regras da RFC 9700 sobre redirect URIs, grants obsoletos e sender-constrained tokens

O OAuth 2.0 existe para responder a uma pergunta bem específica: como uma aplicação pode chamar uma API em seu nome sem que você entregue a ela sua senha? Antes do OAuth, a resposta honesta era que você passava suas credenciais para a aplicação e torcia para dar certo — o que significava que cada integração tinha as chaves da sua conta inteira e não podia ser revogada individualmente. O OAuth substitui isso por um token — restrito, com prazo de validade, revogável — emitido depois que você aprova uma solicitação específica em um site em que já confia.

O framework é pequeno, mas costuma ser mal interpretado. Ele é um framework de autorização, não um protocolo de autenticação; a especificação propositalmente não diz nada sobre como o usuário provou quem é. Quase todo problema sério de OAuth visto na prática vem de esquecer essa distinção ou de implementar um fluxo que o ecossistema já aposentou. Este guia cobre os papéis, os fluxos que vale a pena usar em 2026, como os tokens se comportam e as regras da RFC 9700 que substituem partes da RFC 6749 original.

Os quatro papéis

  • O resource owner é o usuário dono dos dados — geralmente uma pessoa, às vezes uma organização.
  • O client é a aplicação que quer acesso. Ele é confidencial se consegue guardar um segredo (uma aplicação web server-side) e público se não consegue (um app mobile, uma aplicação que roda no navegador).
  • O authorization server autentica o resource owner, obtém o consentimento e emite os tokens. Ele expõe um authorization endpoint (front channel, navegador) e um token endpoint (back channel, HTTPS direto).
  • O resource server é a API que aceita o token e entrega os dados.

Dois caminhos de comunicação importam o tempo todo. O front channel passa pelo navegador do usuário por meio de redirecionamentos: ele é visível, fica registrado em logs e pode ser manipulado, então nada secreto pode trafegar por ali. O back channel é uma chamada HTTPS direta de servidor para servidor, autenticada e privada. No fundo, projetar qualquer fluxo OAuth é decidir qual valor vai por qual canal.

Authorization code flow com PKCE, passo a passo

Esse é o fluxo a usar tanto em aplicações de navegador quanto em apps mobile e em aplicações web renderizadas no servidor. O Proof Key for Code Exchange (RFC 7636) surgiu como uma correção para mobile; hoje é obrigatório para todos.

Primeiro, o client gera uma string aleatória de alta entropia, o code verifier, e deriva dela um code challenge aplicando hash SHA-256 e codificação base64url. Só o challenge passa pelo navegador.

code_verifier  = 43-128 chars of unreserved randomness, e.g. "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
code_challenge = BASE64URL( SHA256( code_verifier ) )

1. Requisição de autorização. O client redireciona o navegador para o authorization endpoint.

GET /authorize?response_type=code
  &client_id=s6BhdRkqt3
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=orders.read%20orders.write
  &state=af0ifjsldkj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256 HTTP/1.1
Host: auth.example.com

O parâmetro state é um valor opaco que o client guarda na sessão e confere no retorno; ele vincula a resposta à requisição e impede ataques de cross-site request forgery contra o endpoint de redirecionamento.

2. Autenticação e consentimento. O servidor de autorização autentica o usuário do jeito que preferir — senha mais um segundo fator, uma passkey, uma sessão já existente — e mostra uma tela de consentimento listando os scopes solicitados. O client não participa de nada disso.

3. Redirecionamento de volta com um código.

HTTP/1.1 302 Found
Location: https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj

O authorization code é de uso único, tem vida curta (de alguns segundos a um ou dois minutos) e sozinho não serve para nada.

4. Requisição de token pelo back channel. Agora o client prova que foi ele quem iniciou o fluxo enviando o verifier original.

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

5. Resposta com o token.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDMifQ...",
  "token_type": "Bearer",
  "expires_in": 300,
  "refresh_token": "8xLOxBtZp8",
  "scope": "orders.read orders.write"
}

O servidor de autorização recalcula SHA256(code_verifier) e compara com o challenge que armazenou. Um atacante que de alguma forma tenha interceptado o código no front channel não consegue resgatá-lo, porque o verifier nunca saiu do client legítimo.

6. Chamada à API. O access token vai no header Authorization, como define a RFC 6750, e o resource server o valida antes de fazer qualquer outra coisa.

GET /orders/1042 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDMifQ...

Como essa validação deve ser feita — verificação da assinatura, validação das claims, rotação de chaves — é o assunto de proteger APIs REST com JWT. Se você precisar inspecionar um token durante uma depuração, um decodificador de JWT que roda no navegador mostra as claims sem enviá-lo para lugar nenhum.

Os outros grants, e os que deixaram de existir

Client credentials é para chamadas máquina a máquina em que não há usuário algum: um job noturno, um serviço chamando outro. O client se autentica e recebe um token que representa a si mesmo.

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=ledger.post

Aqui não há refresh token — o client simplesmente pede de novo — e nenhuma lógica de autorização específica de usuário pode ser associada a esse token, porque ele não representa nenhum usuário.

O device authorization grant (RFC 8628) atende dispositivos com entrada limitada: uma TV, uma impressora, uma ferramenta de linha de comando. O dispositivo pede um código, exibe um código de usuário curto e uma URL, e consulta o token endpoint periodicamente enquanto o usuário conclui o fluxo no celular ou no notebook.

POST /device_authorization HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

client_id=tv-app&scope=video.play
{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://auth.example.com/device",
  "expires_in": 1800,
  "interval": 5
}

O refresh token grant troca um refresh token por um novo access token sem interação do usuário. Clients públicos devem usar rotação de refresh tokens: cada uso devolve um novo refresh token e invalida o anterior, e, se um token já usado reaparecer, o servidor revoga a cadeia inteira, já que esse padrão indica que uma cópia foi roubada.

Dois grants da especificação original agora são proibidos pela RFC 9700. O implicit grant devolvia o access token direto no fragmento da URL, de onde ele vazava para o histórico do navegador, para os referrers e para qualquer script da página; o code flow com PKCE cobre o mesmo caso de uso com segurança. O resource owner password credentials grant obrigava o client a manipular a senha do usuário, o que anula a própria razão de ser do framework e impede autenticação multifator e step-up. É essa consolidação de regras que o mercado chama de OAuth 2.1.

Scopes, consentimento e audience

Um scope é um rótulo de granularidade grossa para uma permissão que o client está pedindo — orders.read, profile, payments.initiate. O servidor de autorização decide quais scopes um determinado client pode sequer solicitar, o usuário consente e o resource server os faz valer. Três cuidados:

  • Scopes descrevem o que o client pode tentar, não o que o usuário pode fazer. Um token com orders.read ainda exige que a API verifique se esse usuário pode ler este pedido. Pular essa verificação é o bug de controle de acesso relacionado a OAuth mais comum.
  • Mantenha poucos scopes, e que façam sentido. Centenas de scopes muito granulares produzem telas de consentimento ilegíveis, e consentimento que ninguém lê não é consentimento.
  • Use o audience (ou um resource indicator) para que um token emitido para uma API não possa ser reaproveitado contra outra. Um token aceito em todo lugar transforma o comprometimento de um único serviço em comprometimento total.

Em clients first-party, as organizações muitas vezes dispensam a tela de consentimento; é uma decisão de política tomada pelo servidor de autorização, não algo que o client possa presumir.

Tempo de vida dos tokens, introspecção e revogação

Access tokens devem ter vida curta — minutos, não dias — porque são bearer credentials: quem tiver um pode usá-lo. Refresh tokens têm vida longa e, por isso, precisam ser armazenados com cuidado e vinculados ao client.

Os tokens vêm em dois formatos. Um token autocontido (normalmente um JWT assinado) pode ser validado offline por qualquer resource server que tenha as chaves públicas do emissor, o que escala bem, mas significa que a revogação não é instantânea. Um token opaco é um identificador aleatório que o resource server precisa consultar, o que permite revogação imediata ao custo de uma chamada de rede a cada requisição; a introspecção da RFC 7662 define essa consulta.

POST /introspect HTTP/1.1
Host: auth.example.com
Authorization: Basic cmVzb3VyY2Utc2VydmVyOnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

token=mF_9.B5f-4.1JqM
{ "active": true, "scope": "orders.read", "client_id": "s6BhdRkqt3", "sub": "u-88231", "exp": 1789012345 }

Um meio-termo prático é usar JWTs de vida curta e revogar o refresh token, aceitando uma janela limitada durante a qual uma sessão revogada ainda funciona. Quando revogação instantânea é um requisito rígido — pagamentos, acesso administrativo —, um API gateway pode fazer a introspecção de forma centralizada e manter o resultado em cache por alguns segundos.

Regras de segurança que não são opcionais

  • Registre redirect URIs exatas e compare-as por igualdade exata de string. Wildcards e correspondência por prefixo são o caminho pelo qual tokens acabam redirecionados para caminhos controlados por um atacante. Um open redirect em qualquer ponto do host registrado reintroduz o mesmo problema.
  • Sempre envie PKCE, sempre confira o state. Eles defendem contra ataques diferentes: o PKCE vincula o código ao client, o state vincula a resposta à sessão.
  • Nunca coloque tokens na query string. URLs acabam em logs de acesso, proxies e headers Referer.
  • Vincule os tokens a quem os apresenta quando o risco justificar. O vínculo a um certificado de TLS mútuo (RFC 8705) ou o DPoP (RFC 9449) torna um token roubado inútil sem a chave correspondente.
  • Valide o parâmetro iss na resposta de autorização quando o client falar com mais de um servidor de autorização, para evitar ataques de mix-up.
  • Não use um access token para identificar o usuário. Use o OpenID Connect e o ID token dele, que é emitido para o seu client e validado por ele.

Do lado da implementação, a maioria dos frameworks já faz a coisa certa por padrão: o Spring Security envia PKCE tanto para clients públicos quanto para confidenciais e valida o state por você. Só escreva o fluxo por conta própria se for inevitável e, nesse caso, siga a especificação ao pé da letra.

Principais conclusões

  • O OAuth 2.0 delega acesso a uma API sem compartilhar credenciais; ele propositalmente não diz nada sobre como o usuário se autenticou.
  • Valores que passam pelo navegador são públicos; só o back channel pode transportar segredos, e é por isso que o code flow existe.
  • Use o authorization code flow com PKCE em todo client interativo, client credentials em chamadas máquina a máquina e o device grant em dispositivos com entrada limitada.
  • Os grants implicit e password foram removidos pela RFC 9700; refresh tokens de clients públicos precisam ser rotacionados e revogados em cadeia em caso de reutilização.
  • Scopes limitam o que um client pode tentar — a API ainda precisa autorizar o usuário em relação ao recurso específico.
  • Correspondência exata de redirect URIs, state, access tokens de vida curta e restrição de audience transformam uma integração que funciona em uma integração segura.

Fontes e leituras complementares

Perguntas frequentes

O OAuth 2.0 é um protocolo de login?
Não. É um framework de autorização delegada: ele produz um access token dizendo que um client pode chamar uma API com determinados scopes, e não diz nada padronizado sobre quem é o usuário nem quando ele se autenticou. Usar um access token como prova de login é um antipattern conhecido, porque o token foi emitido para uma API, não para a sua aplicação. O OpenID Connect adiciona a camada de identidade que responde a essas perguntas.
Clients confidenciais ainda precisam de PKCE?
Sim. A RFC 9700 exige PKCE em toda troca de authorization code, e não só para clients públicos. Um client secret prova qual aplicação está resgatando o código, mas o PKCE também vincula esse código à sessão de navegador específica que iniciou o fluxo, e é isso que impede ataques de injeção e de interceptação de código em ambientes de redirecionamento compartilhados.
Onde uma single-page application deve guardar seus tokens?
De preferência, em lugar nenhum. O padrão recomendado é um backend-for-frontend que faz a troca do código no servidor e mantém os tokens lá, entregando ao navegador apenas um cookie de sessão HttpOnly. Se a SPA precisar mesmo guardar um token, mantenha-o em memória, e não no localStorage, use tempos de vida curtos com rotação de refresh tokens e aceite que qualquer injeção de script na página pode exfiltrá-lo.
O que substituiu os grants implicit e password?
As boas práticas atuais removem os dois. Aplicações de navegador usam o authorization code flow com PKCE, que nunca coloca um token no fragmento de uma URL nem o expõe ao histórico do navegador ou a referrers. Aplicações first-party que usavam o resource owner password credentials grant devem redirecionar para o servidor de autorização, para que autenticação multifator, step-up e consentimento fiquem concentrados em um só lugar.