Fluxos OAuth 2.0 Código de Autorização, PKCE e Clientes de Credenciais
Um guia para desenvolvedores sobre escolher a correta fluxo OAuth 2.0. Aborda o fluxo de autorização (aplicativos web), PKCE (aplicativos de página única e móveis) e credenciais do cliente (servidor para servidor), com exemplos de código funcionais e erros que afetam você mais tarde.
Três fluxos cobrem 95% de casos reais de uso do OAuth 2.0. A especificação define mais, mas os demais estão desatualizados, são casos extremos ou ambos. Escolha o certo desde o início e você pula uma refatoração dolorosa quando as exigências de autenticação mudam.
| Fluxo | Quando usá-lo | Envolve o usuário? | Precisa de client_secret? |
|---|---|---|---|
| Código de Autorização | Aplicativo web com servidor backend | Sim | Sim — permanece no servidor |
| Código de Autorização + PKCE | Aplicativo SPA, app móvel, qualquer cliente público | Sim | Não |
| Credenciais do Cliente | Entre serviços (sem usuário) | Não | Sim — permanece no servidor |
Fluxo de Código de Autorização
O fluxo padrão para aplicativos web com backend. O token de acesso e o cliente secreto nunca tocam o navegador — a troca do token ocorre no lado do servidor. Isso é o ponto principal.
Etapa 1: Redirecione o usuário
Construa uma URL de autorização e redirecione o navegador para ela:
GET https://accounts.example.com/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid+profile+email
&state=RANDOM_CSRF_TOKEN
O state o valor é sua defesa contra CSRF no redirecionamento. Gerar uma string aleatória fresca por fluxo, armazenar na sessão e verificar quando o usuário retornar. Se você pular essa verificação, um atacante poderá conduzir o usuário por um fluxo usando o código de autorização do atacante — vinculando silenciosamente a conta do usuário à identidade do atacante.
Etapa 2: Trate a resposta de callback
O servidor de autorização redireciona de volta para seu redirect_uri com um código de curta duração:
GET https://yourapp.com/callback?code=AUTH_CODE&state=SAME_STATE_YOU_SENT
Verificar state corresponde ao que você armazenou. Em seguida, troque o código no lado do servidor.
Etapa 3: Troque o código por tokens (apenas no lado do servidor)
POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
A resposta:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200a12b3c...",
"scope": "openid profile email"
}
Armazene ambos os tokens no lado do servidor. Quando access_token expira (verifique expires_in), use o refresh_token para obter um novo sem precisar fazer o usuário passar novamente pelo login.
PKCE — Para Clientes que Não Podem Manter Segredos
Um SPA ou app móvel não tem um local seguro para um client_secret. Qualquer pessoa pode abrir DevTools e encontrá-lo no seu bundle JS. Qualquer pessoa pode decompilar seu APK. O PKCE (Proof Key for Code Exchange, pronunciado "pixy") resolve isso com um desafio criptográfico único — sem necessidade de segredo compartilhado.
O fluxo é idêntico ao de Código de Autorização com duas adições: um code_verifier (string aleatória que você gera) e um code_challenge (hash SHA-256 do verificador, codificado em base64url). Você envia o desafio antecipadamente e prova que possui o verificador no momento da troca.
Etapa 1: Gerar o verificador e o desafio
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64url(array);
}
async function generateCodeChallenge(verifier) {
const data = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return base64url(new Uint8Array(digest));
}
function base64url(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier in memory — NOT localStorage
Armazene o code_verifier na memória — uma variável de escopo de módulo, não no localStorage. Você o enviará no momento da troca do token.
Etapa 2: Solicitação de autorização — adicione o desafio
GET https://accounts.example.com/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid+profile
&state=RANDOM_CSRF_TOKEN
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
Etapa 3: Troca do token — verificador em vez de client_secret
POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=ORIGINAL_VERIFIER_YOU_GENERATED
O servidor de autorização hasha o verificador e o compara com o desafio que você enviou na etapa 2. Um atacante que interceptou o código de autorização não sabe o que é o verificador — não pode usá-lo para trocar.
Valor a saber: o OAuth 2.1 (a modernização em andamento do OAuth 2.0) exige PKCE para todos os fluxos envolvendo redirecionamentos. Se você estiver escrevendo código novo, use PKCE independentemente de seu provedor exigir isso atualmente.
Credenciais do Cliente — Sem Usuário, Sem Problema
Tarefas em segundo plano, microserviços chamando outros microserviços, tarefas cron que acessam uma API — nenhuma dessas envolve um usuário. O fluxo certo é o de Credenciais do Cliente: o serviço se autentica diretamente usando seu próprio ID e segredo do cliente.
POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=api:read api:write
Resposta:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400
}
Sem token de atualização — quando ele expira, solicite um novo. O erro comum: chamar o ponto de acesso em cada chamada à API. Armazene o token, verifique a expiração antes de cada requisição, e reconsiga apenas quando estiver prestes a expirar. Uma requisição de token por dia (ou por hora, dependendo de expires_in) em vez de uma por requisição:
let cachedToken = null;
let tokenExpiresAt = 0;
async function getAccessToken() {
// Refresh 30 seconds before actual expiry
if (cachedToken && Date.now() < tokenExpiresAt - 30_000) {
return cachedToken;
}
const res = await fetch('https://accounts.example.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
scope: 'api:read api:write',
}),
});
const data = await res.json();
cachedToken = data.access_token;
tokenExpiresAt = Date.now() + (data.expires_in * 1000);
return cachedToken;
}
Erros que Desenvolvedores Fazem de Fato
Armazenar tokens no localStorage
Qualquer vulnerabilidade XSS — em seu próprio código, em uma dependência, em um script de gerenciador de tags — pode ler tudo no localStorage. Para SPAs: armazene o token de acesso na memória (uma variável de escopo de módulo que desaparece ao recarregar). Use cookies httpOnly para tokens de atualização quando tiver um backend que os possa definir. O JavaScript não consegue ler cookies httpOnly.
Usar o fluxo implícito
O fluxo implícito retorna tokens diretamente na fragmentação da URL (#access_token=...). Esses tokens acabam na história do navegador, nos logs de acesso do servidor e nos cabeçalhos Referer. Foi descontinuado na RFC 9700. Não há razão para usar o fluxo implícito em código novo. Use PKCE.
Pular a validação do estado
Baixar como .yml state A validação na resposta de callback, um atacante pode criar uma URL de redirecionamento que complete um fluxo OAuth usando seu próprio código de autorização. O resultado: a conta do usuário é vinculada à identidade do atacante no provedor. Gerar um novo por fluxo, armazenar na sessão e verificar na resposta de callback.
Colocar client_secret no código do frontend
Não existe tal coisa como um segredo que vive no navegador. A minificação não o oculta. A obfuscção não o protege. Se seu ambiente for um navegador ou um app móvel, você tem um cliente público — use PKCE e omita completamente o client_secret. Isso não é uma solução de contorno; é como o especificação pretende que os clientes públicos funcionem.
Não lidar com a expiração do token de forma proativa
Cada token de acesso tem um expires_in valor. Se você mantiver um token até que ele falhe com um 401 e então re-autentique, os usuários terão erros misteriosos. Verifique a expiração antes de fazer requisições, renove proativamente (30 segundos antes da expiração é um bom buffer) e trate o caso raro em que um token de atualização tenha expirado.
Inspeção de Tokens enquanto você trabalha
A maioria dos provedores de OAuth emite JWTs como tokens de acesso. O payload é codificado em base64url e legível sem a chave privada — apenas a assinatura exige a chave para verificar. Quando você está debugando um fluxo e deseja ver as declarações, escopos ou expiração em um token, cole-o no Decodificador JWT.
Se você estiver testando manualmente PKCE e precisar verificar o que um valor codificado em base64url code_challenge decodifica, o Conversor de Base64 trata variantes padrão e seguras para URLs.
A Versão Curta
Uma pergunta determina o fluxo certo: seu ambiente tem um local seguro para manter um segredo?
- Servidor backend → Código de Autorização ou Credenciais do Cliente (o segredo permanece no servidor)
- Navegador ou app móvel → Código de Autorização + PKCE (sem segredo algum)
- Sem usuário envolvido → Credenciais do Cliente
O PKCE funciona para todos os fluxos envolvendo redirecionamento — não há desvantagem em usá-lo mesmo quando seu provedor ainda não o exige. O OAuth 2.1 o exigirá.
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente foi adicionado em 12 de junho de 2026
