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.
| Item | OAuth 2.0 | OpenID Connect |
|---|---|---|
| Objetivo | Autorização (Authorization) | Autenticação (Authentication) |
| Pergunta respondida | ”Você permite que este app acesse seus recursos?" | "Quem é você?” |
| O que é obtido | Access Token | ID Token + Access Token |
| Ano de criação | 2012 (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
| Scope | Claims retornadas |
|---|---|
openid | sub (obrigatório) |
profile | name, family_name, given_name, picture, etc. |
email | email, email_verified |
address | address |
phone | phone_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:
| Token | Validade |
|---|---|
| Access Token | 15 min ~ 1 hora |
| Refresh Token | 7 ~ 30 dias (validade absoluta) |
| ID Token | 5 min ~ 1 hora |
| Código de Autorização | Menos 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
- Uso de PKCE (especialmente para SPA/mobile)
- Proteção CSRF com parâmetro State
- Proteção contra replay attack com Nonce
- Armazenamento de tokens com HTTPOnly Cookie
- Validade curta do access token
Compreendendo e implementando corretamente esses conceitos, você pode construir sistemas de autenticação seguros e fáceis de usar.
Links de Referência
- RFC 6749 - OAuth 2.0
- RFC 7636 - PKCE
- OpenID Connect Core 1.0
- OAuth 2.0 Security Best Current Practice