Chaves de API
Autenticação entre sistemas, com geração, armazenamento, escopo e rotação das chaves.
O problema: sistemas não digitam senha
O tópico de autenticação trata de pessoas: um login, uma sessão, um token que expira em minutos. Mas boa parte do tráfego de uma API vem de outros sistemas: o ERP do parceiro que consulta pedidos de madrugada, o script de integração, o servidor de outra empresa. Eles não fazem login nem respondem a um segundo fator.
A solução mais comum é a chave de API: um segredo longo, gerado pelo seu sistema, que o cliente envia a cada pedido.
GET /pedidos HTTP/1.1
Host: api.loja.exemplo.com
Authorization: Bearer loja_live_jDosv23if64z0lM97juGvKwxStdHurfvUoRmL6EiuqIÉ simples, e funciona com qualquer linguagem, ferramenta e curl. Os problemas aparecem no ciclo de vida: como a chave é gerada, guardada, limitada, trocada e, principalmente, o que acontece quando ela vaza.
Gerar: aleatória e longa
A chave do exemplo tem um prefixo e 32 bytes aleatórios, 256 bits, codificados em base64url. Com 256 bits, adivinhar uma chave válida está fora de alcance, mesmo com milhões de chaves cadastradas. A geração precisa usar o gerador criptográfico da linguagem (crypto.randomBytes, secrets, crypto/rand, SecureRandom, RandomNumberGenerator), e não um Math.random ou um UUID versão 1, que é previsível.
Guardar: só o hash, e um hash rápido
Se a tabela de chaves vazar num backup ou numa invasão, todas as integrações ficam expostas. Por isso, como as senhas, a chave é guardada só como hash: o servidor calcula o hash da chave recebida e procura esse hash na tabela. A chave em si aparece para o cliente uma única vez, na criação.
A diferença para as senhas está no algoritmo. Senhas são curtas e previsíveis, e precisam de um hash lento, como mostra o tópico de hash de senhas. Uma chave de 256 bits aleatórios não pode ser adivinhada com dicionário nenhum, então um hash rápido basta, e o custo de um lento aparece em cada pedido. Medimos num núcleo:
| Hash | Tempo por verificação | Verificações por segundo |
|---|---|---|
| SHA-256 | 0,0013 ms | cerca de 750 mil |
bcrypt, custo 10 (bcryptjs, JavaScript puro) | 66 ms | 15 |
Com bcrypt, cada pedido autenticado gastaria 66 ms de CPU, e um núcleo atenderia 15 pedidos por segundo. Com SHA-256, a verificação é desprezível. E o SHA-256 ainda permite buscar a chave por um índice único no banco, porque o mesmo valor sempre gera o mesmo hash. O bcrypt usa um sal aleatório e obrigaria a comparar a chave recebida com cada linha da tabela.
Enviar: no cabeçalho, nunca na URL
Algumas APIs aceitam a chave na URL: /pedidos?api_key=.... É prático para testar no navegador, e é um vazamento garantido. A URL aparece em logs de proxies, de balanceadores, de ferramentas de monitoramento e no histórico do navegador.
Testamos com um Nginx na configuração padrão de logs, com um pedido de cada forma:
"GET /pedidos?api_key=loja_live_SEGREDO_NA_URL HTTP/1.1" 200 3 "-" "curl/8.5.0"
"GET /pedidos HTTP/1.1" 200 3 "-" "curl/8.5.0"A chave na URL foi parar no log. A do cabeçalho Authorization, não. Os exemplos da Implementação recusam a chave na URL, mesmo válida.
Quando vaza: o prefixo ajuda a encontrar
Chaves vazam: num commit esquecido, num print enviado ao suporte, num log copiado para um chamado. Quanto antes o vazamento for encontrado, menor o estrago. Ferramentas de varredura de segredos (secret scanning), como o gitleaks e a varredura do GitHub, procuram segredos em repositórios. Para encontrar uma chave, elas precisam reconhecer o formato.
Testamos o gitleaks (versão 8.30) com uma frase comum num chamado de suporte, contendo duas chaves: uma com o prefixo loja_live_ e outra sem prefixo, ambas aleatórias.
| Varredura | Chave com prefixo | Chave sem prefixo |
|---|---|---|
| Regras padrão | Não encontrada | Não encontrada |
Regras padrão e uma regra para loja_(?:live|test)_ | Encontrada | Não encontrada |
As regras padrão só encontraram as duas quando estavam atribuídas a uma variável com nome sugestivo (LOJA_KEY=...) ou num comando curl com o cabeçalho Authorization. No meio de um texto, uma chave sem prefixo é só uma sequência aleatória de caracteres, indistinguível de qualquer outra. Com um prefixo próprio, uma regra simples a encontra em qualquer lugar. É por isso que provedores como Stripe (sk_live_), GitHub (ghp_) e Slack (xoxb-) usam prefixos.
O teste achou também um erro na nossa primeira regra. Com loja_(live|test)_, o gitleaks registrou como segredo só o trecho live, porque usa o primeiro grupo da expressão como o valor do segredo. A regra correta usa um grupo que não captura: (?:live|test).
Limitar o estrago
Uma chave vazada vale até ser revogada. Três decisões limitam o que ela permite:
| Decisão | O que limita |
|---|---|
| Escopos | A chave de consulta de pedidos não cria pedidos nem faz reembolsos |
| Validade | Uma chave com data de expiração para de funcionar sozinha |
| Uma chave por integração | Revogar a chave vazada não derruba as outras integrações do mesmo cliente |
E o registro de uso (quando e de onde cada chave foi usada pela última vez) mostra se uma chave vazada chegou a ser usada, e quais chaves estão abandonadas e podem ser removidas.
Por que isso importa quando a IA escreve o código
Um assistente que implementa "autenticação por chave de API" costuma acertar o fluxo e errar o ciclo de vida:
- A chave guardada em texto puro na tabela, ou criptografada de forma reversível.
- bcrypt na verificação, deixando cada pedido 60 ms mais lento, ou um hash rápido sem índice único.
Math.random, um UUID ou um carimbo de tempo como chave.- A chave aceita na URL, e registrada nos logs.
- Uma chave por cliente, sem escopo nem validade, que não pode ser trocada sem derrubar a integração.
- Nenhum prefixo, e nenhum registro de último uso.
O próximo nível compara a chave de API com as alternativas e mostra como fazer a rotação sem derrubar quem integra.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Chaves de API em cinco minutos
Explique em no máximo cinco minutos de leitura como funcionam as chaves de API, para alguém que já fez login de usuários, mas nunca autenticou outro sistema. Cubra só o essencial: como gerar, por que guardar só o hash, por que a chave não vai na URL, para que serve o prefixo e como escopos e validade limitam o estrago de um vazamento. Use o exemplo de um parceiro que consulta pedidos de uma loja. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
O ciclo de vida de uma chave de API
Quero entender o ciclo de vida completo de uma chave de API, da criação à revogação. Aja como um mentor e vá por etapas, esperando a minha resposta em cada uma: 1. A geração: entropia, gerador criptográfico, formato com prefixo. 2. O armazenamento: por que hash, e por que um hash rápido serve aqui e não serve para senhas. 3. O uso: cabeçalho, escopos, limite de requisições e registro de último uso. 4. A rotação com duas chaves ativas. 5. A revogação, planejada e em caso de vazamento. Em cada etapa, me faça uma pergunta para verificar se entendi antes de seguir.
Como chaves de API vazam
Quero entender os caminhos pelos quais chaves de API vazam e como cada um é detectado. Explique, com um exemplo para cada um: 1. Commits em repositórios, inclusive no histórico. 2. URLs registradas em logs de proxies e ferramentas. 3. Prints e mensagens enviados ao suporte. 4. Variáveis de ambiente expostas em páginas de erro. 5. Chaves embutidas em aplicativos de celular ou no front-end. Para cada um, diga a defesa e como a varredura de segredos ajuda, e por que um prefixo próprio faz diferença.
Chave de API, OAuth2 ou pedido assinado
Quero entender as formas de autenticar um sistema em outro. Compare chave de API, OAuth2 com credenciais do cliente, pedido assinado com HMAC e TLS mútuo quanto a: 1. O que trafega a cada pedido. 2. O que acontece se o segredo ou o token vazar. 3. A dificuldade para quem integra. 4. A infraestrutura necessária de cada lado. Depois me descreva três integrações e pergunte qual forma eu escolheria em cada uma. Corrija as minhas respostas.