Um guia prático para autenticação e autorização de API, incluindo OAuth 2.0, JWT e gerenciamento de chaves de API.
O OAuth 2.0 é a estrutura de autorização mais amplamente usada para acesso à API. A seleção do tipo de concessão adequado é fundamental.
| Tipo de subsídio | Caso de uso | Nível de segurança |
|---|---|---|
| Authorization Code + PKCE | SPAs e aplicativos móveis (recomendado) | Mais alto |
| Authorization Code | Aplicativos da Web no lado do servidor | Alta |
| Client Credentials | Comunicação de servidor para servidor (M2M) | Médio |
| Implícito (obsoleto) | SPAs legados | Baixa |
| Senha do proprietário do recurso (obsoleto) | Somente de terceiros altamente confiáveis | Baixa |
A concessão implícita agora está obsoleta porque o token é exposto no fragmento de URL. Use Código de autorização + PKCE para SPAs.
// 1. gerar aleatoriamente o code_verifier function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // 2. gerar code_challenge com SHA-256 async function generateCodeChallenge(verifier) { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const digest = await crypto.subtle.digest('SHA-256', data); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // Solicitação de autorização const verifier = generateCodeVerifier(); const challenge = await generateCodeChallenge(verifier); const authUrl = `https://auth.example.com/authorize?` + `response_type=code&` + `client_id=${clientId}&` + `redirect_uri=${redirectUri}&` + `code_challenge=${challenge}&` + `code_challenge_method=S256&` + `scope=read write`;
O JWT é amplamente usado como um token de autenticação sem estado, mas erros de implementação levam diretamente a vulnerabilidades de segurança.
const jwt = require('jsonwebtoken'); // --- Emissão de token --- function issueTokens(user) { const accessToken = jwt.sign( { sub: user.id, role: user.role }, process.env.JWT_SECRET, { algorithm: 'HS256', expiresIn: '15m', issuer: 'api.example.com', audience: 'app.example.com', } ); const refreshToken = jwt.sign( { sub: user.id, type: 'refresh' }, process.env.REFRESH_SECRET, { algorithm: 'HS256', expiresIn: '7d' } ); return { accessToken, refreshToken }; } // --- Verificação de token --- function verifyAccessToken(token) { return jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'], // ← Sempre especifique! issuer: 'api.example.com', audience: 'app.example.com', }); }
| Local de armazenamento | Resistência a XSS | Resistência a CSRF | Recomendação |
|---|---|---|---|
| HttpOnly Cookie | ◎ | △ (requer a configuração SameSite) | Recomendado |
| Memória (variável) | ◎ | ◎ | Perda de navegação na página |
| localStorage | ✕ (pode ser roubado via XSS) | ◎ | Não recomendado |
| sessionStorage | ✕ (pode ser roubado via XSS) | ◎ | Uso limitado |
Armazenar o token de acesso em um cookie com atributos HttpOnly, Secure e SameSite=Strict é a abordagem mais segura. Isso evita o roubo de tokens por meio de ataques XSS.
As chaves de API são um mecanismo de autenticação simples, mas o gerenciamento inadequado pode levar a sérios riscos.
Gerar com entropia suficiente (256 bits ou mais). Armazene com hash; nunca mantenha texto simples no banco de dados.
Atribua escopos mínimos por chave (somente leitura, somente recursos específicos, etc.). Evite permissões curinga.
Faça a rotação das chaves regularmente (dentro de 90 dias). Defina um período de carência para chaves antigas. Revogue imediatamente em caso de vazamento.
Envie via cabeçalho (X-API-Key ou Authorization). Não inclua nos parâmetros de consulta de URL (eles aparecem nos registros).
Não codifique as chaves no código-fonte. Gerencie-as com variáveis de ambiente ou com um gerenciador de segredos.
Monitorar o uso da chave da API. Detecte padrões anômalos (solicitações em massa, acesso de IPs desconhecidos).
const crypto = require('crypto'); // Faça o hash da chave da API com SHA-256 e compare async function validateApiKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).json({ error: 'API key required' }); } // Hash e consulta no banco de dados (timingSafeEqual não é necessário, pois comparamos hashes) const hashedKey = crypto .createHash('sha256') .update(apiKey) .digest('hex'); const keyRecord = await db.findApiKey(hashedKey); if (!keyRecord || keyRecord.revokedAt) { return res.status(403).json({ error: 'Invalid API key' }); } // Verificar expiração if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) { return res.status(403).json({ error: 'API key expired' }); } req.apiClient = keyRecord; next(); }
AI agents and LLM-powered applications require additional authentication and authorization patterns beyond traditional API security.
Assign different access levels per model tier (e.g., GPT-4 requires elevated scope). Prevent unauthorized use of expensive or sensitive models.
Embed token budgets in access tokens or API key metadata. Enforce per-request and per-day limits to prevent denial-of-wallet attacks.
Check user permissions before executing certain prompt types (e.g., code generation, data analysis). Map prompt categories to role-based permissions.
Ensure conversation history, fine-tuned models, and RAG data are isolated per tenant. Use tenant-scoped API keys with namespace enforcement.
Assign cryptographic identities to AI agents. Use signed JWTs for inter-agent communication. Verify agent identity before granting tool access.
Automate API key rotation for LLM providers. Use short-lived tokens for agent sessions. Revoke credentials immediately when agents are decommissioned.
| Scenario | Auth Method | Required Scope | Approval |
|---|---|---|---|
| Agent reads public data | API Key | data:read |
Automatic |
| Agent modifies user data | OAuth 2.0 (delegated) | data:write |
User consent required |
| Agent calls external API | OAuth 2.0 + mTLS | external:invoke |
Human-in-the-loop |
| Agent executes code / shell | Scoped JWT + Sandbox | exec:sandbox |
Admin approval + audit log |
import jwt import time from datetime import datetime, timedelta class AgentTokenIssuer: """Issues scoped, short-lived tokens for AI agents.""" ALLOWED_SCOPES = { "reader": ["data:read"], "writer": ["data:read", "data:write"], "executor": ["data:read", "data:write", "exec:sandbox"], } def __init__(self, secret_key: str): self.secret_key = secret_key def issue_agent_token( self, agent_id: str, role: str, tenant_id: str, token_budget: int = 10000, ttl_minutes: int = 15, ) -> str: # Validate role and resolve scopes if role not in self.ALLOWED_SCOPES: raise ValueError(f"Invalid role: {role}") payload = { "sub": agent_id, "tenant": tenant_id, "scopes": self.ALLOWED_SCOPES[role], "token_budget": token_budget, "iat": datetime.utcnow(), "exp": datetime.utcnow() + timedelta(minutes=ttl_minutes), "type": "agent", } return jwt.encode(payload, self.secret_key, algorithm="HS256") def verify_tool_access(self, token: str, required_scope: str) -> dict: # Decode and verify agent token payload = jwt.decode( token, self.secret_key, algorithms=["HS256"] ) if required_scope not in payload["scopes"]: raise PermissionError( f"Agent {payload['sub']} lacks scope: {required_scope}" ) return payload
Related: LLM06: Excessive Agency, ASI01: Excessive Agency, ASI03: Insecure Tool/Function Calling
| Método | Sem estado | Caso de uso | Considerações sobre segurança |
|---|---|---|---|
| OAuth 2.0 + PKCE | ○ | APIs que exigem autorização do usuário | estado/PKCE necessário, gerenciamento da expiração do token |
| JWT Bearer | ○ | Comunicação inter-microsserviços | É necessária a especificação do algoritmo, sem dados confidenciais na carga útil |
| Chave da API | ○ | Servidor para servidor, integrações externas | Rotação e restrição de escopo necessárias |
| mTLS | ○ | Comunicação M2M de alta segurança | Alto custo operacional para o gerenciamento de certificados |
| Cookie de sessão | ✕ | Aplicativos da Web tradicionais | Necessidade de proteção CSRF, desafios de dimensionamento |