Pular para o conteúdo
15 min de leitura

OAuth 2.0 explicado: como um app acessa seus dados sem saber sua senha

Por Lucas Andrade ·

OAuth 2.0 é delegação de acesso, não login; entenda os papéis, o fluxo Authorization Code com PKCE, escopos, tokens e os erros comuns.

Neste artigo

Antes do OAuth, se um aplicativo de terceiros precisava ler seus contatos, publicar em seu nome ou acessar seus arquivos em outro serviço, a solução era literal: você entregava sua senha daquele outro serviço para o aplicativo. Um app de agenda que quisesse sincronizar com seu e-mail pedia usuário e senha do provedor de e-mail, guardava isso em algum banco e usava para logar como se fosse você, sempre que precisasse. Funcionava, no sentido estrito de que os dados chegavam ao destino, mas era uma péssima ideia por três motivos ao mesmo tempo: o app ganhava acesso total, não só ao que precisava; não havia como revogar só aquele acesso sem trocar a senha em todo lugar que a usava; e o número de lugares guardando sua senha crescia a cada integração nova, cada um deles um alvo a mais para vazamento.

OAuth 2.0 nasceu para resolver exatamente esse problema — o que a literatura do protocolo chama informalmente de "problema da senha compartilhada". Este artigo assume que você já sabe o que é autenticação (provar quem você é) e não repete esse assunto; o foco aqui é inteiramente o outro lado da moeda, que o OAuth resolve: autorização delegada — como um app age em seu nome, com um conjunto de permissões que você escolhe, sem nunca ver sua senha e sem poder fazer mais do que o combinado.

Autorização, não autenticação: o mal-entendido número um#

OAuth 2.0 é um framework de autorização, não de autenticação. Essa frase parece pedante até você ver as consequências práticas de ignorá-la. OAuth responde a pergunta "o que este aplicativo tem permissão de fazer em nome deste usuário?" — não responde "quem é esse usuário?". Um token OAuth prova que um determinado acesso foi concedido; ele não é, por si só, uma prova de identidade utilizável para logar alguém em um sistema.

Durante anos, times de engenharia usaram OAuth como se fosse login: o usuário autorizava o app a "ler seu perfil básico", o app pegava esse acesso e concluía "logo, esse é fulano de tal, vou abrir a sessão dele". Isso funciona por acidente, não por desenho — e falha silenciosamente em cenários que o OAuth nunca foi construído para cobrir, como garantir que o token foi emitido para aquele app específico e não expirou de um jeito que ainda permite ações. A confusão foi comum o bastante para gerar uma solução dedicada: o OpenID Connect (OIDC), uma camada de autenticação construída por cima do OAuth 2.0, que adiciona exatamente o que faltava — um token de identidade padronizado (o id_token, tipicamente um JWT) com garantias formais de quem é o usuário e quando ele se autenticou. Guarde essa distinção, porque ela volta no fim do artigo: OAuth autoriza; OIDC autentica. Um usa o outro como base, mas resolvem perguntas diferentes.

Os quatro papéis do protocolo#

Todo fluxo OAuth 2.0 é uma conversa entre quatro partes, e nomear cada uma com precisão evita a maior parte da confusão ao ler a especificação ou a documentação de um provedor.

Resource Owner é o usuário. É a pessoa dona dos dados — sua conta de e-mail, seu perfil, seus arquivos. É quem tem o poder de conceder ou negar acesso, e é a quem o consentimento é pedido.

Client é o aplicativo que quer o acesso. Pode ser um app mobile, uma SPA, um serviço de back-end — qualquer coisa que precise falar com uma API em nome do Resource Owner. O Client nunca vê a senha do usuário; ele só recebe um token, depois de o usuário consentir.

Authorization Server é quem decide e emite tokens. É o componente que autentica o Resource Owner (login e senha, passkey, o que for), apresenta a tela de consentimento, e — se o usuário aprovar — emite os tokens que o Client vai usar. É, tipicamente, um serviço separado do resto da aplicação, com sua própria interface de login.

Resource Server é a API que guarda os dados protegidos. É onde o Client efetivamente usa o token para pedir os dados — ler contatos, postar um tweet, listar arquivos. O Resource Server confia no token porque confia no Authorization Server que o emitiu; ele não precisa saber a senha do usuário para validar o pedido, só precisa validar o token.

Em provedores grandes, Authorization Server e Resource Server costumam ser sistemas distintos dentro da mesma empresa (o servidor de login do Google não é o mesmo processo que serve a API do Google Drive), mas conceitualmente são dois papéis separados mesmo quando implementados pela mesma organização.

Os tokens: um de vida curta, outro que o substitui#

O resultado prático de um fluxo OAuth bem-sucedido são um ou dois tokens, com propósitos e tempos de vida deliberadamente diferentes.

Access token: o que é apresentado à API, e dura pouco. É a credencial que o Client anexa a cada requisição ao Resource Server — normalmente em um cabeçalho Authorization: Bearer <token>. Sua vida costuma ser curta, de minutos a poucas horas, por desenho: se ele vazar, a janela de abuso é pequena. O Resource Server valida o access token a cada chamada; ele não guarda estado sobre a sessão do usuário, só confia no que o token atesta — geralmente o escopo concedido e para qual Client.

Refresh token: de vida longa, e serve só para pedir novos access tokens. Quando o access token expira, o Client não manda o usuário logar de novo — ele apresenta o refresh token ao Authorization Server, que emite um access token novo. Isso mantém a experiência contínua sem re-autenticação constante. A diferença crucial é que o refresh token é revogável no servidor: se o usuário revoga o acesso do app (numa tela de "aplicativos conectados", por exemplo), o Authorization Server invalida o refresh token, e a próxima tentativa de renovação falha — o acesso morre ali, mesmo que o access token anterior ainda não tivesse expirado.

Rotação de refresh token reduz o estrago de um vazamento. Uma prática comum em provedores modernos é invalidar o refresh token antigo a cada uso e emitir um novo junto com o access token — se um refresh token roubado for usado por um atacante e depois pelo Client legítimo (ou vice-versa), a reutilização de um token já trocado é um sinal de comprometimento, e o Authorization Server pode revogar toda a cadeia.

Escopos: o app pede só o que precisa, e você decide#

Scopes (escopos) são a unidade de permissão granular do OAuth. Em vez de um acesso "tudo ou nada", o Client declara, no início do fluxo, exatamente quais permissões está pedindo — read:contacts, write:calendar, profile:email são exemplos de nomes de escopo, embora o formato exato varie por provedor. O Authorization Server mostra esses escopos ao usuário na tela de consentimento, em linguagem legível ("Este aplicativo quer: ver seus contatos. Não quer: enviar e-mails em seu nome"), e o usuário aprova ou nega.

Escopo é a aplicação prática de menor privilégio. Um app de agenda que só precisa ler eventos não deveria pedir escopo de escrita, e um Authorization Server bem desenhado recusa conceder mais do que o Client declarou precisar. Do lado de quem consome uma API de terceiros e constrói a integração, pedir o escopo mínimo necessário não é só boa prática de segurança — é o que faz o usuário confiar o bastante para consentir, porque a tela de permissão é legível e razoável, em vez de uma lista assustadora de acessos genéricos.

O consentimento é um evento auditável, não um clique qualquer. O Authorization Server registra o que foi concedido, para qual Client, com quais escopos, e o usuário pode revisar e revogar depois — essa é a peça que resolve o segundo problema do modelo antigo de senha compartilhada: revogação seletiva, sem precisar trocar credencial nenhuma.

O fluxo principal: Authorization Code com PKCE#

Existem vários fluxos ("grant types") na especificação OAuth 2.0, mas o que qualquer Client novo deveria usar hoje — aplicativo mobile, SPA ou servidor tradicional — é o Authorization Code com PKCE. Vale desenhar passo a passo, porque a sequência é o que evita as duas falhas mais comuns do protocolo: vazamento do código de autorização e roubo de token em trânsito.

```

  1. Client gera code_verifier (string aleatória) e deriva

code_challenge = BASE64URL(SHA256(code_verifier))

  1. Client redireciona o usuário para o Authorization Server:

GET /authorize? response_type=code &client_id=... &redirect_uri=https://app.exemplo.com/callback &scope=read:contacts &state=<valor aleatório único desta sessão> &code_challenge=<code_challenge> &code_challenge_method=S256

  1. Authorization Server autentica o Resource Owner

(tela de login, se ainda não estiver logado)

  1. Authorization Server mostra a tela de consentimento

com os escopos pedidos; usuário aprova

  1. Authorization Server redireciona de volta ao redirect_uri

com um código de uso único: GET https://app.exemplo.com/callback?code=AUTH_CODE&state=...

  1. Client confere que o state devolvido é o mesmo que enviou

(proteção contra CSRF)

  1. Client troca o code por tokens, em uma chamada de back-channel

(servidor a servidor, não pelo navegador): POST /token grant_type=authorization_code &code=AUTH_CODE &redirect_uri=https://app.exemplo.com/callback &client_id=... &code_verifier=<code_verifier original, não derivado>

  1. Authorization Server recalcula o code_challenge a partir do

code_verifier recebido, compara com o que guardou no passo 2, e só então emite access_token (+ refresh_token)

  1. Client usa o access_token nas chamadas ao Resource Server

```

O código de autorização (code) é de uso único e de vida curtíssima — ele não é o token final, é um comprovante de que o usuário consentiu, trocado por tokens de verdade em uma etapa separada. Separar "receber o code" (que passa pelo navegador, um canal mais exposto) de "trocar o code por tokens" (chamada direta servidor-a-servidor, o back-channel) é o que impede que um code interceptado no meio do caminho sirva de algo sozinho — ele exige, além disso, o code_verifier que só o Client original gerou e nunca expôs.

PKCE (Proof Key for Code Exchange) é o que fecha essa brecha. O nome é pronunciado "pixy", e o mecanismo é simples: o Client gera um segredo aleatório (code_verifier) antes de sequer redirecionar o usuário, envia só uma versão derivada e irreversível dele (code_challenge, um hash SHA-256) na ida, e revela o segredo original só na troca final do code por tokens. Se um atacante interceptar o code — por um redirect mal configurado, um log vazado, um app malicioso no mesmo dispositivo registrando o mesmo esquema de URL — ele não tem o code_verifier correspondente, e a troca por tokens falha. PKCE nasceu pensado para clients "públicos" (SPA, mobile, que não conseguem guardar segredo nenhum com segurança), mas a recomendação atual é usá-lo em todo client, inclusive os confidenciais com back-end próprio: a defesa é barata e fecha uma classe inteira de ataque, então não faz sentido reservá-la só para quem "precisa mais".

Por que o Implicit morreu e onde entra o Client Credentials#

O fluxo Implicit devolvia o token direto na URL de redirecionamento, sem etapa de troca — e por isso saiu de uso. Ele existia para clients que, há uma década, não conseguiam fazer uma chamada de back-channel (SPAs muito antigas, essencialmente). O problema é estrutural: um token na fragment da URL fica exposto no histórico do navegador, em logs de proxy, em extensões instaladas, e não passa pela verificação extra que o Authorization Code com PKCE oferece. Hoje, qualquer SPA consegue fazer a troca de code por token via fetch, então o Implicit não tem mais motivo de existir — a recomendação atual da própria evolução da especificação (OAuth 2.1) é removê-lo.

Client Credentials é o fluxo para máquina-a-máquina, sem usuário no meio. Quando quem precisa de acesso não é uma pessoa, mas um serviço de back-end falando com outro — um job agendado, uma integração entre sistemas —, não existe Resource Owner que faça login nem tela de consentimento para mostrar. O Client se autentica direto no Authorization Server com suas próprias credenciais (um client_id e client_secret, ou algo mais forte como um certificado) e recebe um access token que representa o próprio Client, não um usuário. É o fluxo certo para chave de API de servidor-a-servidor, nunca para nada que rode no navegador ou em um app instalado, porque exige guardar um segredo que precisa continuar secreto.

Resource Owner Password Credentials existe na especificação, mas é desencorajado. Nesse fluxo, o Client pede usuário e senha diretamente e os envia ao Authorization Server para trocar por tokens — o que reintroduz exatamente o problema que o OAuth existe para eliminar: o app volta a manusear a senha bruta do usuário, mesmo que só de passagem. Ele só fazia sentido como ponte de migração para sistemas legados que não tinham como redirecionar o usuário para uma tela de login separada, e a orientação atual é evitá-lo — se um provedor exige esse fluxo, é um sinal de alerta sobre a maturidade da implementação.

Erros e riscos que aparecem na prática#

A maior parte dos incidentes envolvendo OAuth não vem de falha criptográfica no protocolo — vem de implementação frouxa em pontos previsíveis.

redirect_uri precisa validar contra uma allowlist exata, nunca por prefixo ou padrão. Se o Authorization Server aceita qualquer URI que "começa com" o domínio registrado, um atacante registra um subdomínio ou usa um caminho aberto a redirecionamento e desvia o code para um servidor próprio. A validação correta é comparação exata, string a string, contra a lista de URIs cadastradas para aquele Client.

state protege contra CSRF na etapa de callback. Sem um state único e verificado, um atacante pode iniciar seu próprio fluxo de autorização e induzir a vítima a completar o callback dele, associando a conta da vítima ao acesso do atacante. Gerar um valor aleatório antes do redirecionamento e conferir que o mesmo valor volta no callback fecha essa via.

HTTPS é obrigatório em cada etapa, sem exceção. Qualquer trecho do fluxo em texto claro — o redirecionamento, a troca do code, a chamada à API — expõe code, tokens ou credenciais a quem estiver no meio do caminho.

Tokens não pertencem a localStorage nem à URL. Um access token guardado em localStorage é lido por qualquer script que rode na página, então uma única falha de XSS entrega a credencial inteira; um token na query string de uma URL acaba em logs de servidor, histórico do navegador e cabeçalho Referer de outras chamadas. O lugar seguro depende da arquitetura do Client, mas em geral significa cookie HttpOnly para o que o navegador precisa guardar, ou manter o token só em memória, nunca em armazenamento persistente acessível a JavaScript.

Access token curto e refresh rotativo é a combinação que limita o dano de um vazamento. Já apareceu antes neste artigo, mas vale repetir como princípio de risco: quanto mais curta a vida do token que efetivamente abre a porta, menor a janela de uso indevido se ele escapar — e a renovação constante via refresh token, revogável a qualquer momento, é o que torna essa vida curta viável sem incomodar o usuário.

"Entrar com Google": OIDC por cima do OAuth#

O botão "Entrar com Google" ou "Continuar com GitHub" que aparece em quase todo formulário de cadastro moderno é, tecnicamente, um fluxo OAuth 2.0 — com a camada OIDC por cima. O app (Client) redireciona para o Authorization Server do Google, o usuário se autentica e consente, e o app recebe de volta, além do access token de OAuth, um id_token — um JWT assinado que contém as alegações de identidade (quem é o usuário, quando se autenticou, com qual conta de e-mail verificada). É esse id_token, formalizado pelo OIDC, que o app usa para abrir a sessão local do usuário — não o access token do OAuth, que continua servindo só para chamar as APIs do Google que o usuário autorizou (ler o perfil, por exemplo).

Essa é a distinção prática que fecha o mal-entendido do início do artigo: se o objetivo é logar alguém, o padrão correto é OIDC, e o id_token é a peça central. Se o objetivo é agir em nome de alguém em uma API — ler os contatos, postar um evento na agenda, listar arquivos —, o padrão é OAuth puro, e o access token é a peça central. Um sistema real de "login social" quase sempre usa os dois ao mesmo tempo, e é exatamente por isso que a confusão é tão persistente: eles chegam juntos, na mesma tela de consentimento, na mesma troca de tokens — mas resolvem perguntas diferentes, e tratar um pelo outro é o erro que este artigo tentou prevenir.

Fechando#

OAuth 2.0 existe para acabar com a prática de entregar sua senha a quem só precisava de um pedaço específico do seu acesso, por um tempo limitado, revogável a qualquer momento. Ele formaliza isso em quatro papéis — Resource Owner, Client, Authorization Server, Resource Server —, em dois tipos de token com vidas diferentes — access token curto, refresh token revogável —, e em escopos que tornam o consentimento específico e legível. O fluxo que qualquer client novo deveria implementar é o Authorization Code com PKCE: redirecionamento para login e consentimento, retorno com um código de uso único, troca desse código por tokens em um canal separado, protegida por um segredo que só o client original possui. Os erros que derrubam implementações reais não estão na criptografia do protocolo — estão em redirect_uri validado por prefixo, state ausente, token guardado onde script nenhum deveria alcançar. E a distinção que vale carregar para fora deste artigo é a mais simples de todas: OAuth decide o que um aplicativo pode fazer em seu nome; para decidir quem você é, o protocolo certo é o OpenID Connect, construído logo em cima. Confundir os dois é o mal-entendido mais comum do assunto — e agora você sabe onde termina um e começa o outro.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly