Como Funcionam OAuth 2.0 e OpenID Connect - Princípios de Design de Autenticação e Autorização

18 min leitura | 2025.12.02

Em aplicações web e mobile modernas, login social como “Entrar com Google” ou “Entrar com GitHub” se tornou comum. Por trás disso estão duas especificações padrão: OAuth 2.0 e OpenID Connect (OIDC). Este artigo explica esses mecanismos em detalhes, do básico ao avançado.

Diferença entre OAuth 2.0 e OIDC

Primeiro, vamos esclarecer a diferença entre OAuth 2.0 e OIDC, que frequentemente são confundidos.

ItemOAuth 2.0OpenID Connect
ObjetivoAutorização (Authorization)Autenticação (Authentication)
Pergunta respondida”Você permite que este app acesse seus recursos?""Quem é você?”
O que é obtidoAccess TokenID Token + Access Token
Ano de criação2012 (RFC 6749)2014

Diferença entre Autenticação e Autorização:

  • Autenticação (Authentication): Processo de confirmar “quem é o usuário”
  • Autorização (Authorization): Processo de determinar “o que o usuário pode acessar”

Os Atores do OAuth 2.0

O OAuth 2.0 possui 4 papéis (roles) principais.

flowchart TB
    RO["Resource Owner<br/>(Proprietário do Recurso/Usuário)<br/>Dono dos dados. Geralmente o usuário final."]
    AS["Authorization Server<br/>(Servidor de Autorização)<br/>Emite tokens. Google, GitHub, etc."]
    Client["Client<br/>(Cliente)<br/>Aplicação que quer acessar os recursos."]
    RS["Resource Server<br/>(Servidor de Recursos)<br/>Hospeda os recursos protegidos. APIs, etc."]

    RO -->|Concede autorização| AS
    AS -->|Emite token| Client
    Client -->|Requisição API| RS

Tipos de Grant do OAuth 2.0

O OAuth 2.0 define múltiplos “grant types” (fluxos) para diferentes casos de uso.

1. Authorization Code Grant (Grant de Código de Autorização)

O fluxo mais seguro e recomendado. Usado em aplicações server-side.

sequenceDiagram
    participant U as User
    participant C as Client
    participant AS as Auth Server
    participant RS as Resource Server

    U->>C: 1. Inicia login
    C->>AS: 2. Requisição de autorização
    AS->>U: 3. Tela de login e consentimento
    U->>AS: 4. Autenticação e consentimento
    AS->>C: 5. Código de autorização
    C->>AS: 6. Troca de token
    AS->>C: 7. Access token
    C->>RS: 8. Chamada de API
    RS->>C: 9. Recurso
// Step 2: Geração da URL de autorização
const authorizationUrl = new URL('https://auth.example.com/authorize');
authorizationUrl.searchParams.set('response_type', 'code');
authorizationUrl.searchParams.set('client_id', CLIENT_ID);
authorizationUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authorizationUrl.searchParams.set('scope', 'openid profile email');
authorizationUrl.searchParams.set('state', generateRandomState());

// Step 6: Troca de token
const tokenResponse = 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: authorizationCode,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
  }),
});

2. Authorization Code Grant com PKCE

Extensão para SPAs e aplicativos móveis, onde o client secret não pode ser armazenado com segurança.

// PKCE: Proof Key for Code Exchange
import crypto from 'crypto';

// Step 1: Geração do Code Verifier (string aleatória de 43-128 caracteres)
const codeVerifier = crypto.randomBytes(32)
  .toString('base64url');

// Step 2: Geração do Code Challenge
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

// Step 3: Adicionar code_challenge à requisição de autorização
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('scope', 'openid profile');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateState());

// Step 4: Enviar code_verifier na troca de token
const tokenResponse = 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: authorizationCode,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    code_verifier: codeVerifier,  // Envia verifier em vez do secret
  }),
});

3. Client Credentials Grant

Usado para comunicação servidor-a-servidor (M2M: Machine to Machine). Sem envolvimento de usuário.

// Autenticação entre serviços backend
const tokenResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
  },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    scope: 'api:read api:write',
  }),
});

const { access_token } = await tokenResponse.json();

4. Refresh Token Grant

Fluxo para atualizar o access token.

// Atualização do access token via refresh token
const refreshResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken,
    client_id: CLIENT_ID,
  }),
});

const { access_token, refresh_token } = await refreshResponse.json();
// Se um novo refresh token for retornado, atualizar o armazenamento

OpenID Connect (OIDC)

OIDC é uma camada de autenticação construída sobre o OAuth 2.0.

Estrutura do ID Token

No OIDC, além do access token, é emitido um ID Token (formato JWT).

flowchart TB
    subgraph JWT["Estrutura do ID Token (JWT)"]
        subgraph Header["Header (Cabeçalho)"]
            H1["alg: RS256, typ: JWT, kid: key-id-123"]
        end

        subgraph Payload["Payload (Carga)"]
            P1["iss: Emissor"]
            P2["sub: Subject"]
            P3["aud: Audience"]
            P4["exp: Expiração"]
            P5["iat: Data de emissão"]
            P6["nonce: Proteção contra replay"]
            P7["email, name: Informações do usuário"]
        end

        subgraph Signature["Signature (Assinatura)"]
            S1["Assinado com a chave privada do servidor de autorização"]
        end

        Header --> Payload --> Signature
    end

Validação do ID Token

import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

// Configuração do cliente JWKS (JSON Web Key Set)
const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
});

// Função para obter a chave pública
const getKey = (header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => {
  client.getSigningKey(header.kid, (err, key) => {
    callback(err, key?.getPublicKey());
  });
};

// Validação do ID Token
const verifyIdToken = (idToken: string): Promise<jwt.JwtPayload> => {
  return new Promise((resolve, reject) => {
    jwt.verify(
      idToken,
      getKey,
      {
        algorithms: ['RS256'],
        issuer: 'https://auth.example.com',
        audience: CLIENT_ID,
      },
      (err, decoded) => {
        if (err) reject(err);
        else resolve(decoded as jwt.JwtPayload);
      }
    );
  });
};

Endpoint UserInfo

Usando o access token, é possível obter informações adicionais do usuário.

const userInfoResponse = await fetch('https://auth.example.com/userinfo', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
  },
});

const userInfo = await userInfoResponse.json();
// {
//   "sub": "user-123",
//   "name": "João Silva",
//   "email": "user@example.com",
//   "email_verified": true,
//   "picture": "https://example.com/avatar.jpg"
// }

Design de Scopes

Scopes Padrão do OIDC

ScopeClaims retornadas
openidsub (obrigatório)
profilename, family_name, given_name, picture, etc.
emailemail, email_verified
addressaddress
phonephone_number, phone_number_verified

Exemplo de Scopes Customizados

// Scopes customizados para acesso à API
const scopes = [
  'openid',           // Obrigatório OIDC
  'profile',          // Perfil básico
  'email',            // Endereço de email
  'api:read',         // Permissão de leitura na API
  'api:write',        // Permissão de escrita na API
  'admin:users',      // Permissão de gerenciamento de usuários
];

Melhores Práticas de Segurança

1. Proteção CSRF com Parâmetro State

// Gera um state único por sessão
const generateState = (): string => {
  const state = crypto.randomBytes(32).toString('hex');
  // Salva na sessão
  session.oauthState = state;
  return state;
};

// Valida o state no callback
const validateState = (receivedState: string): boolean => {
  const isValid = receivedState === session.oauthState;
  delete session.oauthState;  // Remove após uso
  return isValid;
};

2. Proteção contra Replay Attack com Nonce

// Inclui nonce na requisição do ID Token
const nonce = crypto.randomBytes(16).toString('hex');
session.oidcNonce = nonce;

authUrl.searchParams.set('nonce', nonce);

// Verifica nonce na validação do ID Token
const decoded = await verifyIdToken(idToken);
if (decoded.nonce !== session.oidcNonce) {
  throw new Error('Invalid nonce');
}

3. Armazenamento Seguro de Tokens

// Backend: Gerenciamento de sessão com HTTPOnly Cookie
res.cookie('session_id', sessionId, {
  httpOnly: true,      // Inacessível via JavaScript
  secure: true,        // HTTPS obrigatório
  sameSite: 'lax',     // Proteção CSRF
  maxAge: 3600000,     // 1 hora
});

// Frontend (SPA): Manter em memória
// LocalStorage é vulnerável a XSS, então evite
class TokenStore {
  private accessToken: string | null = null;

  setToken(token: string) {
    this.accessToken = token;
  }

  getToken() {
    return this.accessToken;
  }
}

4. Design de Validade dos Tokens

Validades recomendadas:

TokenValidade
Access Token15 min ~ 1 hora
Refresh Token7 ~ 30 dias (validade absoluta)
ID Token5 min ~ 1 hora
Código de AutorizaçãoMenos de 10 minutos

Discovery Document

Provedores OIDC fornecem um endpoint Discovery para obtenção automática de configurações.

// Obtém configuração do endpoint Well-known
const discoveryUrl = 'https://auth.example.com/.well-known/openid-configuration';

const config = await fetch(discoveryUrl).then(r => r.json());
// {
//   "issuer": "https://auth.example.com",
//   "authorization_endpoint": "https://auth.example.com/authorize",
//   "token_endpoint": "https://auth.example.com/token",
//   "userinfo_endpoint": "https://auth.example.com/userinfo",
//   "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
//   "scopes_supported": ["openid", "profile", "email"],
//   "response_types_supported": ["code", "token", "id_token"],
//   ...
// }

Resumo

OAuth 2.0 e OIDC são tecnologias fundamentais para autenticação e autorização web modernas.

Pontos-chave do OAuth 2.0

  • Objetivo: Autorização (delegação de permissões de acesso a recursos)
  • Fluxo recomendado: Authorization Code Grant + PKCE
  • Tokens: Access token, Refresh token

Pontos-chave do OIDC

  • Objetivo: Autenticação (verificação da identidade do usuário)
  • Elemento adicional: ID Token (formato JWT)
  • Scopes padrão: openid, profile, email, etc.

Requisitos de Segurança Essenciais

  1. Uso de PKCE (especialmente para SPA/mobile)
  2. Proteção CSRF com parâmetro State
  3. Proteção contra replay attack com Nonce
  4. Armazenamento de tokens com HTTPOnly Cookie
  5. Validade curta do access token

Compreendendo e implementando corretamente esses conceitos, você pode construir sistemas de autenticação seguros e fáceis de usar.

← Voltar para a lista