Authorization

OAuth 2.0

OAuth 2.0 é um framework para autorização delegada, não para autenticação. Ele permite que um usuário conceda a um app acesso limitado aos seus dados sem compartilhar a senha.

intermediate16 min readUpdated 16 de set. de 2026
authorize.ts
ts
// authorize.ts
import { randomBytes, createHash } from "node:crypto";

const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");

const url = new URL("https://auth.example.com/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: "web-app",
  redirect_uri: "https://app.example.com/callback",
  scope: "openid email profile offline_access",
  state: randomBytes(16).toString("base64url"),
  code_challenge: challenge,
  code_challenge_method: "S256",
}).toString();

console.log(url.toString());
Primeira spec
2012
RFCs principais
RFC 6749 / 6750
RFC do PKCE
RFC 7636
Funções
4
Grant moderno
Authorization Code + PKCE
Camada de identidade
OpenID Connect

Por que importa

Para que serve o OAuth

Acesso delegado

Um usuário concede a uma aplicação acesso limitado e revogável aos seus dados sem nunca entregar uma senha ou uma credencial de longo prazo.

Permissões com escopo

Cada concessão solicita scopes explícitos, para que um client receba exatamente o acesso ao qual o usuário consentiu e nada mais.

Um padrão entre provedores

O mesmo fluxo funciona com Google, GitHub, Okta e seu próprio servidor, o que significa um único padrão de integração para qualquer provedor de identidade.

O panorama completo

As quatro funções em uma imagem

Um resource owner concede a um client acesso a um resource server, intermediado por um authorization server que emite tokens.

Resource owner

Conceder

O usuário que possui os dados e decide qual aplicação pode acessá-los e para quê.

Client

Solicitar

A aplicação que solicita o acesso, identificada por um client id e, quando for capaz de armazenar um, um secret.

Authorization server

Intermediar

Autentica o usuário, exibe o consentimento e emite tokens de acesso, refresh e identidade.

Resource server

Hospedar

A API que detém os dados protegidos e só os retorna após aceitar um access token válido.

HTML5 de uma olhada

As peças móveis

Authorize endpoint

O redirecionamento do navegador onde o usuário se autentica e consente.

Authorization code

Um código de curta duração e uso único retornado para a redirect URI do client.

Token endpoint

Um POST de back-channel que troca um código ou refresh token por tokens.

Redirect URI

O callback registrado, correspondido exatamente para evitar o roubo de códigos.

Resource server

A API que aceita um bearer access token e retorna os dados.

Scopes

Permissões delimitadas por espaços que restringem o que um token pode fazer.

Fluxo

Authorization Code + PKCE

O fluxo padrão para web, mobile e single-page apps. Dois redirecionamentos e uma troca via back-channel.

  1. 1

    Redirecionamento com um challenge

    O client envia o navegador para /authorize com seu client id, redirect URI, scopes, state e um code_challenge PKCE com hash.

  2. 2

    O usuário consente

    O authorization server autentica o usuário e exibe os scopes solicitados. O usuário aprova ou nega.

  3. 3

    Redirecionamento de volta com um código

    O navegador retorna para a redirect URI registrada com um authorization code de curta duração e uso único, além do state original.

  4. 4

    Troca de código mais verifier

    O client faz um POST do código e do code_verifier original para o token endpoint e recebe tokens de acesso, refresh e ID.

  5. 5

    Chamada ao resource server

    O client envia o access token como uma credencial Bearer em requisições de API. O resource server o valida e aplica os scopes.

  6. 6

    Refresh quando expirar

    Quando o access token expira, o client troca o refresh token por um novo par e, em seguida, revoga os tokens no logout.

Uma breve historia

De requisições assinadas ao PKCE

  1. 2007

    OAuth 1.0 e requisições assinadas

    Uma spec inicial que assina cada requisição com segredos compartilhados, o que é seguro, mas trabalhoso para clients e provedores.

    07
  2. 2012

    OAuth 2.0 substitui assinaturas

    A RFC 6749 introduz bearer tokens e o modelo baseado em funções que ainda define o protocolo hoje.

    12
  3. 2014

    OpenID Connect adiciona identidade

    O OIDC adiciona um ID token e um userinfo endpoint sobre o OAuth, permitindo o login real.

    14
  4. 2015

    PKCE protege clients públicos

    A RFC 7636 fecha a brecha de interceptação de código para apps que não conseguem manter um client secret.

    15
  5. 2021

    OAuth 2.1 consolida as lições

    O rascunho deprecia os grants implicit e password e torna o PKCE obrigatório, codificando as melhores práticas.

    21

O guia completo

OAuth 2.0: Tudo que voce precisa saber

O que o OAuth 2.0 é, e o que não é

O OAuth 2.0 é um framework de autorização. Ele permite que um usuário conceda a um aplicativo de terceiros acesso limitado aos seus recursos sem a necessidade de compartilhar sua senha. O resultado de um fluxo bem-sucedido é um access token, e esse token descreve o que o cliente pode fazer, e não necessariamente quem o usuário é.

Essa distinção confunde as pessoas constantemente. O “Sign in with Google” é o OAuth 2.0 somado ao OpenID Connect. A parte do OAuth obtém acesso às APIs do Google; a parte do OpenID Connect retorna um ID token que efetivamente identifica o usuário. Se você implementar apenas o OAuth e tratar o access token como prova de identidade, você terá construído um acesso delegado e o chamado de autenticação, o que é um erro de categoria com consequências de segurança.

O protocolo foi projetado para um problema específico: permitir que um site de impressão de fotos leia suas fotos de um provedor de armazenamento sem nunca ver a sua senha do armazenamento. Mantenha essa origem em mente e as escolhas de design farão sentido.

OpenID Connect: identidade sobreposta

O OpenID Connect é uma camada fina de identidade sobre o OAuth 2.0. Ele adiciona o escopo openid, um endpoint userinfo padronizado e, mais importante, um ID token, um JWT cujas claims descrevem o evento de autenticação.

{
  "iss": "https://accounts.example.com",
  "sub": "110169484474386276334",
  "aud": "web-app",
  "exp": 1760000000,
  "iat": 1759999100,
  "email": "[email protected]",
  "email_verified": true,
  "nonce": "n-0S6_WzA2Mj"
}

O ID token é destinado ao cliente, não à API. Nunca o envie para um servidor de recursos como se fosse um access token. Verifique sua assinatura, iss, aud, exp e nonce antes de confiar nele, e utilize o sub, e não o e-mail, como o identificador estável do usuário. Endereços de e-mail mudam e são reatribuídos; o subject é estável durante toda a vida da conta.

As quatro funções

O OAuth define quatro participantes, e ser preciso sobre eles torna o restante do protocolo óbvio.

  • Resource owner — o usuário que possui os dados e concede o acesso.
  • Client — a aplicação que solicita o acesso, por exemplo, seu web app.
  • Authorization server — emite tokens após autenticar o usuário e obter o consentimento.
  • Resource server — a API que aceita o access token e retorna os dados.

Seu web app é o client. O Google é o authorization server. As APIs do Google são o resource server. O usuário é o resource owner. Um único provedor geralmente desempenha ambos os papéis de servidor, e é por isso que a distinção pode parecer acadêmica até que você construa a sua própria.

Tipos de grant

Um grant type é a “receita” que um cliente utiliza para obter um token. Quatro deles são relevantes hoje em dia.

  • Authorization Code + PKCE — o padrão para web, mobile e single-page apps. O usuário se autentica no servidor de autorização e o cliente troca um código de curta duração por tokens.
  • Client Credentials — machine-to-machine. Nenhum usuário está envolvido; o cliente se autentica com suas próprias credenciais.
  • Device Code — para dispositivos com entrada limitada, como televisões e ferramentas de CLI.
  • Refresh Token — não é uma forma de fazer login, mas a maneira padrão de renovar um access token sem a necessidade de outro redirecionamento.

Dois grants estão efetivamente obsoletos. O fluxo implicit retornava tokens diretamente no fragmento da URL, expondo-os ao histórico e referrers. O grant password solicitava que o cliente manipulasse a senha do usuário, o que anula todo o propósito do OAuth. O OAuth 2.1 remove ambos, e nenhum sistema novo deve utilizá-los.

Authorization Code + PKCE

Este é o fluxo que você deve aprender. O cliente redireciona o usuário para o servidor de autorização com um challenge hasheado, o usuário consente, o servidor redireciona de volta com um código de uso único e o cliente troca esse código, junto com o verifier original, por tokens.

O código é inútil sem o verifier, portanto, um atacante que intercepte o redirecionamento não conseguirá completar a troca. É isso que torna o PKCE seguro mesmo para clientes públicos, como apps mobile e single-page apps, que não conseguem manter um secret de forma segura.

O fluxo possui dois redirecionamentos de navegador e uma requisição de back-channel. Os redirecionamentos são visíveis e podem ser manipulados; a troca é um POST direto de servidor para servidor que um atacante não consegue observar. Manter o material secreto no back channel é a base de todo o design.

A URI de redirecionamento e o state

A redirect URI é para onde o servidor de autorização envia o usuário de volta. Ela deve ser registrada e validada com precisão exata. Validações permissivas são a causa de vulnerabilidades de open-redirect que vazam códigos de autorização para hosts controlados por atacantes. Nunca aceite uma redirect URI vinda de um query parameter e não permita subdomínios com wildcard.

O parâmetro state é um valor opaco que o cliente gera e verifica quando o callback retorna. Ele protege o endpoint de callback contra CSRF. Um atacante que engane a vítima para completar um fluxo com o código do atacante falhará na verificação do state.

const state = randomBytes(16).toString("base64url");
session.oauthState = state;

// later, in the callback:
if (query.state !== session.oauthState) {
  throw new Error("state_mismatch");
}

Para OIDC, adicione um nonce e verifique-o dentro do ID token. O state protege o callback do cliente contra CSRF; o nonce vincula o ID token a esta requisição específica e bloqueia ataques de replay.

PKCE em detalhes

O PKCE (Proof Key for Code Exchange) é definido pela RFC 7636 e agora é obrigatório para clientes públicos. Ele consiste em três valores: o cliente gera um code_verifier aleatório, deriva um code_challenge como seu hash SHA-256, envia o challenge na requisição de autorização e envia o verifier na requisição do token. O servidor então gera o hash do verifier e faz a comparação.

import { randomBytes, createHash } from "node:crypto";

const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");

Use sempre o método S256. O método plain existe apenas por questões de compatibilidade e anula o propósito do PKCE, já que um atacante que veja o challenge verá também o verifier. Armazene o verifier na sessão do usuário para que o callback possa encontrá-lo, e delete-o após um único uso.

Construindo a requisição de autorização

A requisição de autorização é um redirecionamento do navegador, portanto, trata-se de um GET com parâmetros de query. O usuário verá a tela de consentimento do provedor, e não a sua aplicação.

export function buildAuthorizeUrl(state: string, challenge: string) {
  const url = new URL("https://auth.example.com/authorize");
  url.search = new URLSearchParams({
    response_type: "code",
    client_id: "web-app",
    redirect_uri: "https://app.example.com/callback",
    scope: "openid email profile offline_access",
    state,
    code_challenge: challenge,
    code_challenge_method: "S256",
  }).toString();
  return url.toString();
}

response_type=code seleciona o fluxo de código de autorização (authorization code flow). O escopo openid transforma a requisição em uma requisição OIDC. offline_access é a convenção comum para solicitar um refresh token, e alguns provedores exigem um prompt de consentimento extra para liberá-lo.

Troca de tokens

O callback entrega code e state. Após a verificação do state, o cliente faz um POST do code para o endpoint de token. Esta é uma chamada de back-channel feita pelo seu servidor, e não um redirecionamento do navegador.

export async function exchangeCode(code: string, verifier: string) {
  const res = await fetch("https://auth.example.com/token", {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://app.example.com/callback",
      client_id: "web-app",
      code_verifier: verifier,
    }),
  });

  if (!res.ok) throw new Error("token_exchange_failed");
  return res.json();
}

A resposta contém access_token, token_type, expires_in, geralmente refresh_token e, para OIDC, um id_token. Um cliente confidencial, aquele que consegue armazenar um secret, também se autentica nesta requisição com client_secret ou, preferencialmente, uma asserção de chave privada. Um cliente público depende exclusivamente do PKCE.

Tokens de acesso, refresh e ID

Esses três tokens possuem funções diferentes, e confundi-los causa bugs sutis.

  • Access token — apresentado ao servidor de recursos. Frequentemente é um JWT, mas a especificação exige apenas que ele seja opaco para o cliente. Pode ser apenas uma string aleatória que o servidor consulta.
  • Refresh token — apresentado apenas ao servidor de autorização para obter um novo access token. Possui longa duração e é altamente sensível.
  • ID token — um JWT do OIDC que informa ao cliente quem acabou de fazer login. Ele nunca é enviado para uma API.

Trate o access token como uma credencial de portador (bearer credential): qualquer pessoa que o possua pode utilizá-lo. Mantenha o tempo de vida curto, solicite os scopes mais restritos possíveis e deixe que o refresh token gerencie o relacionamento de longa duração. O formato do token em si é abordado no guia de JWT.

Escopos e consentimento

Os escopos expressam o que o cliente está solicitando. O servidor de autorização os apresenta ao usuário em uma tela de consentimento e codifica o subconjunto concedido no access token.

Solicite o mínimo necessário. Um app de calendário que pede acesso total à caixa de entrada assustará os usuários e aumentará o raio de impacto em caso de uma violação. Os provedores também publicam escopos reservados: openid é obrigatório para OIDC, e offline_access geralmente controla os refresh tokens.

Escopos não são roles. Um escopo descreve uma capacidade que o cliente solicitou para esta concessão; uma role descreve quem o usuário é dentro do seu sistema. Faça o mapeamento entre eles no seu servidor e nunca assuma que os escopos de um provedor dizem algo sobre o seu próprio modelo de autorização. Para esse lado do problema, veja RBAC.

Chamando o servidor de recursos

Com um access token em mãos, as chamadas para a API o transportam no header Authorization utilizando o esquema Bearer.

const res = await fetch("https://api.example.com/me", {
  headers: { authorization: `Bearer ${accessToken}` },
});

O servidor de recursos valida o token, seja verificando um JWT localmente ou através de introspecção, checa o scope e retorna os dados. Ele nunca vê o refresh token ou o ID token, e deve rejeitá-los caso os receba.

Introspecção e revogação

Nem todo access token é um JWT. Quando ele é opaco, o resource server pergunta ao authorization server se ele ainda é válido usando a introspecção de token (token introspection), definida pela RFC 7662.

POST /introspect HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client credentials>

token=2YotnFZFEjr1zCsicMWpAA

A introspecção retorna active, além do scope, subject e expiração. Ela é autoritativa, mas custa uma chamada de rede, portanto, faça o cache do resultado por alguns segundos.

A revogação (revocation), definida pela RFC 7009, permite que um cliente informe ao authorization server para invalidar um token, geralmente no logout.

await fetch("https://auth.example.com/revoke", {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ token: refreshToken, client_id: "web-app" }),
});

Revogue refresh tokens agressivamente no logout e lembre-se de que revogar um refresh token nem sempre invalida access tokens já emitidos. Tempos de vida curtos para o access token são o que fazem a revogação parecer imediata.

Machine-to-machine: client credentials

Quando não há um usuário envolvido, o cliente atua por conta própria. Ele se autentica no endpoint de token e recebe um access token com o escopo de suas próprias permissões.

const res = await fetch("https://auth.example.com/token", {
  method: "POST",
  headers: {
    "content-type": "application/x-www-form-urlencoded",
    authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}`,
  },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    scope: "reports:read",
  }),
});

Não há tela de consentimento nem refresh token; o serviço simplesmente solicita um novo token quando o anterior expira. Prefira a autenticação de cliente via private-key JWT em vez de um shared secret quando o provedor oferecer suporte, e rotacione os secrets periodicamente. Este é o mesmo nicho ocupado por API keys de longa duração, porém com expiração e escopo padronizados, conforme abordado em API Keys.

Quando não usar OAuth

O OAuth serve para delegar acesso a terceiros. Se o seu próprio web app está autenticando seus próprios usuários contra seu próprio backend, você não precisa de OAuth. Um session cookie, ou um JWT de primeira parte emitido por você mesmo, é mais simples e fácil de proteger. Colocar um servidor de autorização entre seu formulário de login e seu banco de dados traz complexidade, não segurança.

Recorra ao OAuth quando você integrar um provedor externo, atuar como um provedor para clientes de terceiros ou precisar de um padrão para acesso machine-to-machine. Caso contrário, comece com Session Auth e adicione OAuth apenas quando surgir uma necessidade real de delegação. Como cada fluxo aqui funciona através de redirecionamentos e headers, o guia de HTTP é um complemento útil.

Armadilhas comuns

  • Implicit flow — tokens no fragmento da URL vazam através do histórico, logs e referrers. Use Authorization Code com PKCE.
  • Tokens no localStorage — um bug de XSS torna-se um roubo de conta (account takeover). Mantenha os tokens em cookies httpOnly ou sessões no server-side.
  • Open redirects — a validação permissiva da redirect URI permite que um atacante roube códigos. Faça a correspondência exata com a URI registrada.
  • Ignorar o state — sem ele, o callback fica vulnerável a CSRF.
  • Scopes excessivamente amplos — solicitar tudo torna o consentimento irrelevante e agrava as consequências de uma violação.
  • Access tokens de longa duração — eles não podem ser revogados rapidamente. Mantenha-os curtos e faça a rotação de refresh tokens.
  • Usar o ID token como credencial de API — ele serve para o cliente, não para o resource server.

Escolhendo o fluxo correto

A maioria das decisões de integração se resume a duas perguntas: existe um usuário e o cliente consegue manter um segredo?

  • Há um usuário presente, o cliente é público (SPA, app mobile, desktop): Authorization Code com PKCE.
  • Há um usuário presente, o cliente é confidencial (web app com renderização no servidor): Authorization Code com PKCE mais autenticação de cliente.
  • Sem usuário, o cliente é um serviço (cron job, microserviço): Client Credentials.
  • O dispositivo não possui navegador ou teclado (TV, CLI): Device Code.

Todo o resto é legado. Se um provedor documenta apenas o fluxo implicit, encare isso como um sinal de alerta e verifique se o PKCE está disponível.

O padrão backend-for-frontend

O lugar mais seguro para armazenar tokens é em um servidor que você controla. O padrão backend-for-frontend coloca um servidor leve entre o navegador e o servidor de autorização: o navegador recebe um cookie de sessão, e o servidor armazena os access e refresh tokens.

app.get("/callback", async (req, res) => {
  if (req.query.state !== req.session.oauthState) {
    return res.status(400).json({ error: "state_mismatch" });
  }

  const tokens = await exchangeCode(
    String(req.query.code),
    req.session.codeVerifier,
  );

  req.session.tokens = tokens;
  delete req.session.oauthState;
  delete req.session.codeVerifier;

  res.redirect("/dashboard");
});

O navegador nunca vê um token, portanto, um XSS não consegue roubá-lo, e o servidor pode realizar o refresh silenciosamente. A contrapartida é um salto extra e a necessidade de manter um servidor rodando, o que geralmente sai mais barato do que o incidente que você evita.

Clientes nativos e mobile

Apps nativos não conseguem manter um client secret, portanto, o PKCE não é opcional. Eles também enfrentam um problema de redirecionamento: um scheme customizado, como myapp://callback, pode ser reivindicado por um app malicioso. A solução moderna é utilizar uma aba do navegador do sistema, seja através da API de sessão de autenticação da plataforma ou de um navegador incorporado que compartilhe cookies com o sistema.

Nunca utilize um WebView incorporado para OAuth. O app poderá ler a senha do usuário, o que quebra a barreira de confiança que todo o protocolo visa proteger. Abra o navegador do sistema, receba o callback e armazene os tokens no armazenamento seguro da plataforma.

Executando seu próprio servidor de autorização

Você não precisa construir um do zero. Servidores consolidados como Keycloak, Ory Hydra e provedores de identidade na nuvem já implementam o protocolo, telas de consentimento, gerenciamento de chaves e armazenamento de tokens para você. Construir o seu próprio é um projeto de vários meses cujos modos de falha são todos críticos para a segurança.

Se você optar por executar o seu próprio, as responsabilidades mínimas são: correspondência exata de redirect URI, imposição de PKCE, tempos de vida curtos para access-tokens, rotação de refresh-tokens com detecção de reuso, um endpoint JWKS e revogação. Esquecer qualquer um desses pontos significa que você entregou uma vulnerabilidade, não uma funcionalidade.

Testando uma integração OAuth

Testes end-to-end de OAuth são lentos e frágeis porque dependem de um provedor real e de um navegador real. Em vez disso, teste em camadas.

  • Faça testes unitários da construção de URLs, derivação de PKCE e comparação de state como funções puras.
  • Faça testes de integração da troca de tokens contra um servidor de autorização mock que retorne tokens pré-definidos.
  • Mantenha um único smoke test contra o provedor real, marcado para que não seja executado em cada commit.
test("builds an authorize URL with PKCE", () => {
  const url = new URL(buildAuthorizeUrl("state-1", "challenge-1"));
  expect(url.searchParams.get("response_type")).toBe("code");
  expect(url.searchParams.get("code_challenge_method")).toBe("S256");
  expect(url.searchParams.get("state")).toBe("state-1");
});

O callback é onde os bugs se escondem, portanto, teste os caminhos de falha explicitamente: um state incompatível, um código reutilizado e um token expirado devem produzir erros claros, em vez de uma sessão parcialmente autenticada.

Logout e single sign-on

Fazer logout da sua aplicação não é o mesmo que fazer logout do provedor. Um logout completo realiza três ações: destrói a sessão local, revoga o refresh token no servidor de autorização e, no caso de single sign-on, encerra a sessão do provedor para que o próximo login não a reutilize silenciosamente.

app.post("/logout", async (req, res) => {
  if (req.session.tokens?.refresh_token) {
    await revoke(req.session.tokens.refresh_token);
  }
  req.session.destroy(() => res.redirect("/"));
});

O single sign-on funciona com a mesma mecânica: assim que um usuário possui uma sessão no provedor, os clientes subsequentes recebem um código sem a necessidade de inserir a senha novamente. Isso é conveniente, e é também por isso que o logout em computadores compartilhados é mais importante do que as pessoas imaginam.

Rotação de refresh token e detecção de reuso

O OAuth não exige que os refresh tokens sejam de uso único, mas as implementações mais robustas utilizam a rotação. Cada atualização retorna um novo refresh token e invalida o anterior. Se um token antigo for apresentado novamente, o servidor identifica que ele foi roubado e revoga toda a família de tokens.

export async function refresh(grant: { refresh_token: string; client_id: string }) {
  const stored = await db.refreshToken.findByHash(hash(grant.refresh_token));

  if (!stored || stored.revoked) {
    if (stored) await revokeFamily(stored.familyId);
    throw new Error("invalid_grant");
  }

  await db.refreshToken.revoke(stored.id);
  return issueTokens(stored.userId, stored.familyId);
}

A detecção de reuso é a grande vantagem: um refresh token roubado torna-se um gatilho de alerta em vez de uma porta dos fundos permanente. Combine isso com uma expiração deslizante (sliding expiry) para que um usuário ativo permaneça conectado, enquanto um token abandonado eventualmente expire.

Melhores práticas

  • Use Authorization Code com PKCE para todo cliente voltado ao usuário, incluindo SPAs e mobile.
  • Use Client Credentials para comunicação machine-to-machine, com autenticação via private-key sempre que possível.
  • Registre redirect URIs exatas e rejeite qualquer URI que não coincida caractere por caractere.
  • Sempre envie e verifique state; adicione e verifique um nonce para OIDC.
  • Solicite os scopes mínimos e trate a tela de consentimento como algo relevante.
  • Mantenha access tokens com vida curta, rotacione refresh tokens e revogue-os no logout.
  • Verifique a assinatura do ID token, iss, aud, exp e nonce antes de confiar nele.
  • Mantenha client secrets e private keys em um secret manager, nunca no frontend.
  • Faça cache de introspection por curtos períodos e dependa de lifetimes curtas para revogações tempestivas.

Erros comuns

  • Tratar OAuth como autenticação sem o OpenID Connect.
  • Implementar o implicit flow apenas por ter um redirecionamento a menos.
  • Armazenar access tokens no localStorage e enviá-los para qualquer origin.
  • Colocar o client secret em uma single-page app onde qualquer pessoa pode lê-lo.
  • Aceitar qualquer redirect URI, ou uma fornecida via query parameter.
  • Esquecer de validar state no callback.
  • Solicitar todos os scopes possíveis e nunca revisar a lista.
  • Assumir que revogar um refresh token invalida instantaneamente os access tokens.
  • Usar o ID token como um bearer token para sua API.

Próximos passos

O OAuth é a forma como os tokens são obtidos; o guia de JWT explica como eles são construídos e verificados. Se o seu app é first-party e você precisa de revogação imediata, Session Auth é o ponto de partida mais simples. Para clientes de máquina que não envolvem um usuário, API Keys aborda a alternativa de longa duração. E como cada um desses fluxos trafega pela rede, vale a pena dar uma revisada em HTTP.

Na pratica

Autorizar, trocar, chamar, atualizar

O ciclo de vida completo do authorization code em quatro requisições.

authorize.ts
import { randomBytes, createHash } from "node:crypto";

export function buildAuthorizeUrl(session: { state?: string }) {
  const verifier = randomBytes(32).toString("base64url");
  const challenge = createHash("sha256").update(verifier).digest("base64url");
  const state = randomBytes(16).toString("base64url");

  session.state = state;
  session.codeVerifier = verifier;

  const url = new URL("https://auth.example.com/authorize");
  url.search = new URLSearchParams({
    response_type: "code",
    client_id: "web-app",
    redirect_uri: "https://app.example.com/callback",
    scope: "openid email profile offline_access",
    state,
    code_challenge: challenge,
    code_challenge_method: "S256",
  }).toString();

  return url.toString();
}

Authorization Code + PKCE vs Implicit

O PKCE mantém os tokens fora da URL e do histórico do navegador, e funciona para clients públicos que não podem guardar um secret.

Preferir
// Browser is redirected, then the backend exchanges the code.
const url = new URL("https://auth.example.com/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: "web-app",
  redirect_uri: "https://app.example.com/callback",
  scope: "openid email",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
}).toString();
Evitar
// Tokens land in the URL fragment where history,
// logs and referrers can leak them.
const url = new URL("https://auth.example.com/authorize");
url.search = new URLSearchParams({
  response_type: "token",
  client_id: "web-app",
  redirect_uri: "https://app.example.com/callback",
  scope: "openid email",
}).toString();

scopes vs roles

Um scope é o que o client solicitou nesta concessão. Uma role é o que o usuário é dentro do seu sistema. Não confunda os dois.

Scope
// Requested capability, granted through consent.
const scope = "reports:read reports:export";

if (!req.user.scope.includes("reports:read")) {
  return res.status(403).json({ error: "insufficient_scope" });
}
Role
// Position in your own authorization model.
const role = "analyst";

// A provider scope never substitutes for a local
// role check; map scopes to roles on your server.
if (!["analyst", "admin"].includes(req.user.role)) {
  return res.status(403).json({ error: "forbidden" });
}

Trade-offs

Você deve construir sobre OAuth?

OAuth é a ferramenta certa para delegação e integração, e a ferramenta errada para login de primeira parte (first-party).

Strengths

  • Sem compartilhamento de senhas

    Usuários concedem acesso com escopo através do provedor em que já confiam e podem revogá-lo sem alterar suas credenciais.

  • Uma integração, muitos provedores

    O mesmo fluxo de authorization code funciona entre provedores, então adicionar um segundo provedor de identidade é majoritariamente configuração.

  • Revogável por design

    Tokens de acesso e refresh podem ser revogados, e a curta vida útil dos access tokens limita o raio de impacto de um vazamento.

Trade-offs

  • Não é autenticação

    O OAuth sozinho prova que um client pode acessar um recurso, não quem o usuário é. Você precisa do OpenID Connect para identidade.

  • Erros fatais são comuns

    Redirect URIs permissivas, ausência de state e tokens armazenados no navegador levam diretamente ao roubo de contas se forem negligenciados.

  • A complexidade tem um custo

    Executar seu próprio authorization server implica em gestão de chaves, telas de consentimento, armazenamento de tokens e revisão de segurança contínua.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender OAuth 2.0?

Nosso tutorial interativo te guia por OAuth 2.0 passo a passo — com quizzes e codigo real que voce pode executar no navegador.