O que é uma API key
Uma API key é uma string secreta de longa duração que identifica uma aplicação em vez de uma pessoa. O cliente a envia em cada requisição, o servidor a reconhece e o acesso é concedido de acordo com as permissões atribuídas àquela chave. Essa é a ideia central.
Trata-se de uma credencial deliberadamente simples. Não há login, tela de consentimento ou troca de tokens. O desenvolvedor se cadastra, cria uma chave, a cola em um arquivo de configuração e o código começa a funcionar. Esse baixo atrito é o motivo pelo qual quase todas as plataformas para desenvolvedores — de pagamentos, mapas, e-mail, infraestrutura — distribuem chaves.
Mas simples não significa descuidado. Uma chave é uma credencial de portador (bearer credential): quem a possui pode utilizá-la, exatamente como dinheiro em espécie. Não há segundo fator nem assinatura. Isso significa que a segurança de todo o sistema depende de quão bem você gera as chaves, com que cuidado você as armazena, quão restrito é o escopo delas e quão rápido você consegue revogar uma que tenha vazado. Este guia trata de como fazer essas quatro coisas da maneira correta.
Quando uma chave é a ferramenta certa
Chaves não são a resposta para todo problema de autenticação, e utilizá-las no lugar errado cria riscos reais.
Opte por uma API key quando um servidor se comunica com outro servidor, quando quem faz a chamada é uma aplicação na qual você confia um segredo de longa duração, ou quando você está oferecendo uma API pública para desenvolvedores. Pipelines de CI, integrações de backend, agentes de monitoramento e serviços de terceiros são casos de uso ideais. A chave é armazenada em um gerenciador de segredos ou em uma variável de ambiente, e nunca chega a tocar um navegador.
Opte por OAuth quando você precisar agir em nome de um usuário. Se a sua integração precisar ler a agenda de alguém ou enviar e-mails como se fosse essa pessoa, você precisará de um fluxo de consentimento e de um token que represente essa delegação. Uma chave não consegue expressar “estes são os dados da Alice e a Alice concordou”.
Opte por um JWT quando precisar de um token verificável e de curta duração que carregue claims e possa ser validado sem a necessidade de um banco de dados compartilhado. Autenticação service-to-service em uma mesh, ou uma URL de download assinada, são bons exemplos.
O caso perigoso é o cliente público. Uma chave incorporada em um app mobile, em um binário de desktop ou em um bundle de navegador não é secreta: qualquer pessoa pode extraí-la. Se o seu produto precisa que esses clientes chamem sua API, coloque um proxy de backend na frente ou emita tokens de curta duração a partir do seu próprio servidor após autenticar o usuário. Nunca envie uma chave de longa duração dentro do código do cliente.
A anatomia de uma boa chave
Uma boa chave é imprevisível e autodescritiva. Ela possui duas partes: um prefixo curto e legível e um secret longo e aleatório.
O prefixo cumpre duas funções. Ele identifica o ambiente e o tipo da chave num relance e fornece ao servidor um identificador indexado para busca sem a necessidade de armazenar o secret. Um formato fixo e reconhecível também torna vazamentos acidentais detectáveis por scanners de secrets, que podem identificar sk_live_ em um commit e bloqueá-lo.
O secret deve originar-se de uma fonte aleatória criptograficamente segura, nunca de Math.random, um timestamp ou um UUID que revele estrutura. 128 bits de entropia é o mínimo; 256 bits é um padrão seguro e não gera custo adicional. A codificação Base64url mantém a chave segura para ser colada em URLs e headers sem a necessidade de escape.
const secret = crypto.randomBytes(32).toString("base64url");
const key = `sk_live_${secret}`;
Resista à tentação de codificar informações na chave, como o ID do usuário ou a data de criação. Qualquer dado legível na chave é uma informação que um atacante obtém, e qualquer coisa derivada de dados previsíveis enfraquece a aleatoriedade. O prefixo é a única parte legível de que você precisa, e ele não deve revelar nada sensível.
Mostre uma vez e depois esqueça
A chave completa deve existir em exatamente um lugar após a criação: na resposta que a retornou ao usuário. A partir desse momento, o servidor armazena apenas um hash e um prefixo, o que significa que ele pode verificar uma chave apresentada, mas nunca poderá reproduzi-la.
Essa é a mesma propriedade do armazenamento de senhas, e isso altera a consequência de uma violação de segurança. Se um invasor fizer um dump da tabela api_keys, ele obterá hashes que não podem ser enviados para a sua API. Sem o hashing, um único vazamento de banco de dados ou exposição de backup entregaria a chave de todos os clientes de uma só vez.
A experiência do usuário decorre dessa restrição técnica. O dashboard mostra a chave apenas uma vez, com um aviso claro para copiá-la agora e, posteriormente, exibe apenas o prefixo e metadados, como escopos e último uso. Se um usuário perder a chave, ele deve rotacioná-la; não é possível recuperá-la. Deixe isso explícito na sua documentação para que ninguém espere encontrá-la mais tarde.
res.status(201).json({
id: row.id,
name: row.name,
prefix: row.prefix,
key, // shown once, never retrievable again
warning: "Store this key now. You will not be able to see it again.",
});
Armazene um hash, nunca a chave
O hashing é o controle mais importante em um sistema de API keys. É também aquele que as equipes mais costumam pular, geralmente porque querem ser capazes de exibir a chave novamente mais tarde. Não faça isso.
Use um hash criptográfico rápido, como o SHA-256. Diferente de senhas, as chaves possuem entropia total, portanto não há dicionários para ataques nem necessidade de um KDF lento. O hash rápido também mantém a verificação barata no caminho crítico (hot path).
function hashKey(key: string): string {
return crypto.createHash("sha256").update(key).digest("hex");
}
A comparação deve ser em tempo constante. Um === ingênuo em strings interrompe a execução no primeiro byte diferente, o que vaza a quantidade de caracteres corretos de uma chave tentada. Isso geralmente é uma preocupação teórica em redes, mas é trivial de evitar e representa uma boa prática de higiene. Compare buffers de mesmo comprimento com crypto.timingSafeEqual.
const a = Buffer.from(hashKey(presented));
const b = Buffer.from(record.keyHash);
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);
Se você quiser esconder o hash completamente do banco de dados, um keyed hash com um pepper no lado do servidor adiciona um segundo segredo que o invasor também precisaria obter. Isso é opcional para sistemas de alta segurança, mas fazer o hashing em si não é opcional.
Buscando chaves sem realizar scan
Existe um problema prático com o hashing: você não consegue consultar o banco de dados pelo hash a menos que faça o hash da chave recebida primeiro, e você não pode fazer o hash da chave recebida até saber com qual linha compará-la. Fazer o hash de cada linha em cada requisição não é uma opção.
A solução é o prefix index. Armazene os primeiros doze caracteres da chave em uma coluna prefix em texto simples com um índice único. Quando uma requisição chega, leia o prefixo, encontre a única linha correspondente, faça o hash da chave completa apresentada e compare-a com o key_hash daquela linha. Uma busca indexada, um hash, uma comparação em tempo constante.
const prefix = key.slice(0, 12);
const record = await db.apiKey.findByPrefix(prefix);
if (!record || record.revokedAt) return res.status(401).end();
const presented = hashKey(key);
if (!timingSafeEqual(presented, record.keyHash)) {
return res.status(401).end();
}
Alguns detalhes mantêm a integridade disso. O prefixo deve ser longo o suficiente para que colisões sejam raras, mas curto o suficiente para ser um rótulo útil; doze caracteres é uma escolha comum. Se ocorrer uma colisão, o índice único falhará na criação e você regenera a chave. Retorne o mesmo erro para um prefixo desconhecido e para um segredo incorreto, para que a resposta não revele se um prefixo existe. E atualize last_used_at de forma assíncrona ou em uma escrita em lote (batch), porque uma atualização síncrona em cada requisição transforma uma leitura em escrita e dobra a carga do seu banco de dados.
Vinculando chaves a permissões e limites
Uma chave que pode fazer tudo é uma chave cujo vazamento é uma catástrofe. Restrinja cada chave ao menor conjunto de permissões que permita que ela execute sua função.
Scopes são apenas strings de permissão, os mesmos átomos usados por RBAC: projects:read, deploy:write, billing:manage. Armazene-os na chave, anexe-os à requisição após a verificação e aplique-os exatamente como você faria com as permissões de um usuário.
router.post(
"/deployments",
apiKeyAuth(),
requireScope("deploy:write"),
createDeployment
);
É aqui que o princípio do menor privilégio se torna concreto. Uma integração de monitoramento precisa apenas de metrics:read. Um pipeline de CI precisa de deploy:write, mas nunca de billing:manage. Um parceiro com acesso apenas de leitura recebe scopes de leitura e nada mais. Quando uma chave vaza, o raio de impacto é limitado ao que foi definido no scope, e é por isso que o padrão deve ser uma lista curta que um humano estenda deliberadamente.
Os scopes também tornam os rate limits naturais e justos. Como cada chave é um chamador distinto, você pode anexar uma cota por chave e aplicá-la na sua camada de rate-limiting, evitando que um script descontrolado consuma a capacidade destinada a todos. Planos em camadas geralmente mapeiam-se diretamente para a cota: chaves gratuitas têm um teto baixo, chaves pagas têm um teto maior.
Prefixos de ambiente
Prefixos não são meros adornos. Eles codificam o ambiente e o tipo de uma chave, o que evita um dos modos de falha mais comuns e embaraçosos: uma chave de teste apontando para produção, ou uma chave de produção usada em uma suíte de testes que acaba alterando dados reais.
Uma convenção familiar é sk_live_ para chaves secretas em produção e sk_test_ para o sandbox. Chaves publicáveis ou públicas podem usar pk_live_. A nomenclatura fica a seu critério, mas mantenha a consistência e documente-a, pois seus usuários dependerão disso para saber, num relance, o que aquela chave pode acessar.
sk_live_... secret key, production, full access within its scopes
sk_test_... secret key, sandbox, no real data
pk_live_... publishable key, safe to embed in a browser
O prefixo também permite que seu servidor direcione as requisições para o ambiente correto antes de qualquer busca. Uma chave sk_test_ apresentada à API de produção pode ser rejeitada imediatamente com uma mensagem clara, em vez de falhar como um erro de autenticação opaco. E, como o prefixo é fixo e distinto, scanners de segredos e ferramentas de code review podem ser configurados para sinalizá-lo.
Enviando uma chave: header ou query
Onde a chave trafega é tão importante quanto a forma como ela é armazenada. Envie-a em um header.
GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...
Headers não são incluídos nos logs de acesso padrão, não aparecem no histórico do navegador e não são encaminhados no header Referer quando uma página linka para outro site. Eles também são fáceis de omitir em logs e proxies. O header Authorization com o esquema Bearer é convencional e suportado pela maioria dos clientes HTTP e ferramentas; um header X-API-Key dedicado funciona igualmente bem.
Query strings são o lugar errado. Elas são capturadas por logs de acesso, cacheadas por intermediários, armazenadas no histórico do navegador e em ferramentas de analytics, e vazam através de Referer. Uma chave em uma URL é uma chave em uma dúzia de lugares que você não controla.
GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1
Se um cliente genuinamente não puder definir headers — como em alguns cenários de webhooks legados ou embeds — use em vez disso um token de curta duração e escopo limitado na query string, e faça-o expirar rapidamente. Nunca aceite uma chave de longa duração em uma URL e, se seus logs puderem conter uma, faça a limpeza (scrubbing).
Rotação e revogação
Toda chave eventualmente precisará ser alterada. Um funcionário sai da empresa, um laptop é perdido, uma chave aparece em um repositório público ou uma política de rotina simplesmente exige a rotação periódica. Planeje isso desde o início, pois implementar a rotação posteriormente é doloroso.
A Rotação emite uma substituição sem interromper o cliente. O padrão é: criar uma nova chave com os mesmos escopos, retorná-la, permitir que ambas as chaves funcionem durante uma curta janela de sobreposição e, então, revogar a antiga. Essa sobreposição é o que torna a rotação zero-downtime, e publicar a duração dessa janela permite que os integradores se planejem.
// 1. Issue the replacement and return it to the owner.
// 2. Keep both keys valid for the overlap window (e.g. 24 hours).
// 3. Revoke the old key, or let it expire automatically.
await db.apiKey.revoke(oldKeyId);
A Revogação é imediata e permanente. Defina revoked_at na linha e faça com que o middleware de verificação rejeite qualquer chave com um valor não nulo. Não delete a linha: mantê-la preserva a trilha de auditoria e evita que o mesmo prefixo seja reutilizado. A revogação deve surtir efeito logo na próxima requisição, o que é fácil quando a chave é verificada no banco de dados e impossível quando se trata de um token autocontido.
Suporte a revogação de uma única chave, de todas as chaves de um usuário e de todas as chaves de um tenant. As duas últimas são o botão “acho que fomos invadidos” e devem ser executadas com um único clique. Alerte o proprietário sempre que uma chave for criada ou revogada, para que um invasor que ganhe acesso ao dashboard não consiga gerar silenciosamente uma nova credencial.
Vazamentos: git, logs e código do cliente
A maioria dos comprometimentos de API key não são ataques sofisticados. Geralmente, é apenas uma chave esquecida em algum lugar onde não deveria estar.
Controle de versão é o caso clássico. Uma chave colada em um arquivo de configuração, em um fixture de teste ou em um .env que é commitado fica no histórico do git para sempre, mesmo que um commit posterior a remova. Escaneie commits e pull requests em busca de padrões de chaves, mantenha segredos em um gerenciador ou variáveis de CI, e faça a rotação imediatamente se alguma chave for commitada. Assuma que uma chave em um repositório público já está comprometida.
Logs são os vazamentos silenciosos. Um logger de requisições que imprime URLs completas, headers ou corpos de requisição pode capturar milhões de chaves. Configure seu logger para redigir headers de Authorization e X-API-Key, nunca logue corpos de requisição em endpoints de autenticação e audite a saída dos logs em busca de strings com formato de chave.
Código do lado do cliente é o erro fatal. Uma chave em um bundle de navegador, em um app mobile ou em um binário de desktop pode ser extraída em minutos. Não existe ofuscação que resolva isso. Coloque um servidor na frente ou emita tokens de curta duração.
Outros lugares para verificar: mensagens de erro e stack traces, analytics de terceiros, screenshots em relatórios de bugs e laptops de desenvolvedores que sincronizam com um backup compartilhado. Trate cada um deles como um possível vazamento e forneça aos usuários as ferramentas para responder quando isso acontecer.
Monitorando uso e anomalias
Uma chave que nunca é observada não pode ser defendida. Registre informações suficientes sobre cada uso para detectar abusos e responder a perguntas após um incidente.
No mínimo, armazene last_used_at e a origem da requisição. A partir disso, você pode criar alertas que capturem os padrões relevantes: uma chave usada pela primeira vez em meses, um salto repentino na taxa de requisições, um país de origem que não condiz com a integração ou uma sequência de erros 401 que sugira que alguém está tentando adivinhar prefixos.
logger.info({
event: "api_key.used",
keyId: record.id,
ownerId: record.ownerId,
route: req.path,
ip: req.ip,
});
Nunca registre a chave em si, apenas o seu id e prefixo. Exiba o uso no dashboard para que os clientes possam ver quais chaves estão ativas e identificar alguma que não reconheçam. Envie e-mails na criação, rotação e revogação, e ofereça aos proprietários uma maneira de desativar instantaneamente uma chave suspeita.
Para um tratamento mais amplo sobre como proteger uma API contra abusos, o guia de rate limiting aborda cotas, tratamento de picos (burst) e como os limites por chave se integram.
Políticas de expiração e ciclo de vida
Uma chave sem expiração é uma chave da qual você esquecerá até que ela vaze. Atribua um ciclo de vida a cada chave, mesmo que o padrão seja generoso.
Uma expiração opcional permite que o usuário crie uma chave que expire em uma data escolhida, o que é ideal para um prestador de serviço, uma integração temporária ou uma migração pontual. Uma idade máxima absoluta limita quanto tempo qualquer chave pode viver, após a qual a rotação torna-se obrigatória. Muitas plataformas combinam as duas abordagens: as chaves têm validade padrão de um ano, podem ter prazos menores e nunca podem exceder dois anos.
Rastreie um estado em vez de apenas um booleano. Uma chave pode estar ativa, prestes a expirar, expirada ou revogada, e cada estado exige uma resposta diferente. Chaves próximas da expiração devem disparar um e-mail para que o proprietário realize a rotação antes de ocorrer uma interrupção, e o middleware de verificação deve tratar chaves expiradas e revogadas de forma idêntica: rejeitar com 401.
function isUsable(key: ApiKey): boolean {
if (key.revokedAt) return false;
if (key.expiresAt && key.expiresAt < new Date()) return false;
return true;
}
A expiração é uma rede de segurança, não um substituto para a revogação. Uma chave roubada que expira em um ano ainda é perigosa, portanto, a rotação e o monitoramento continuam sendo os controles primários. No entanto, um limite de expiração significa que uma chave da qual alguém esqueceu, ou que foi abandonada por um funcionário desligado, eventualmente parará de funcionar por conta própria.
Testando a autenticação por API key
A verificação de API key é uma pequena quantidade de código que protege um grande volume de acesso, portanto, teste-a minuciosamente, focando principalmente em casos negativos.
import request from "supertest";
import app from "../app.js";
test("rejects a request with no key", async () => {
await request(app).get("/v1/projects").expect(401);
});
test("rejects a malformed key", async () => {
await request(app)
.get("/v1/projects")
.set("Authorization", "Bearer not-a-real-key")
.expect(401);
});
test("rejects a revoked key", async () => {
const { key } = await createKey();
await revokeKey(key);
await request(app)
.get("/v1/projects")
.set("Authorization", `Bearer ${key}`)
.expect(401);
});
test("rejects a key without the required scope", async () => {
const { key } = await createKey({ scopes: ["projects:read"] });
await request(app)
.post("/v1/deployments")
.set("Authorization", `Bearer ${key}`)
.expect(403);
});
Adicione testes que provem que a chave completa não é armazenada: crie uma chave, inspecione a linha e assegure que key_hash seja o hash e que o texto simples não apareça em lugar nenhum. Verifique se last_used_at avança ao ser utilizado. E teste explicitamente a sobreposição da rotação: tanto a chave antiga quanto a nova devem funcionar durante a janela de transição, e apenas a nova após o fechamento dela.
Por fim, teste se as respostas de erro não distinguem um prefixo desconhecido de um segredo incorreto. Ambos devem retornar o mesmo status e corpo, caso contrário, você estará dando a um atacante uma maneira de confirmar quais prefixos existem.
Construindo a UI de gerenciamento de chaves
A tela de gerenciamento é onde as propriedades de segurança se tornam visíveis para os usuários, portanto, ela deve tornar o comportamento correto o comportamento mais fácil.
A visualização em lista mostra o nome, prefixo, scopes, data de criação e último uso de cada chave, nunca o segredo. Ela oferece um botão de revogação com confirmação e define a “criação de chave” como o caminho para a rotação. O fluxo de criação permite que o usuário escolha os scopes e uma expiração opcional, exibindo então a chave completa apenas uma vez, com um botão de cópia e um aviso inequívoco.
sk_live_8f2a... CI deploy deploy:write created 3 days ago
sk_live_1c4b... Monitoring metrics:read last used 2 minutes ago
sk_test_9a7d... Staging projects:read revoked yesterday
Bons detalhes para incluir: um timestamp de “último uso” para que os usuários possam identificar chaves que não reconhecem, revogação com um clique para uma única chave, um “revogar todas” para a conta e notificações por e-mail a cada criação e revogação. Se você exibir um gráfico de uso, faça-o por chave, pois essa é a unidade de análise dos usuários.
Não construa um botão de “revelar chave”. Ele não pode existir se você fizer o hash corretamente, e sua ausência é um recurso: isso significa que um vazamento de banco de dados é sobrevivível. Explique na UI que as chaves são exibidas apenas uma vez e que a rotação é o caminho de recuperação, para que a restrição pareça deliberada e não um erro.
Escolhendo a codificação e o comprimento
A codificação é uma decisão simples, mas com algumas consequências práticas. Base64url é a escolha mais comum por ser compacta, segura para URLs e headers, e case-sensitive, o que maximiza a entropia por caractere. Hex é mais longa, porém mais fácil de ler em voz alta e de localizar em logs. Base62 fica entre as duas e evita completamente + e /.
| Codificação | 128 bits | 256 bits | Notas |
|---|---|---|---|
| base64url | 22 chars | 43 chars | Compacta, segura para URL, case-sensitive |
| hex | 32 chars | 64 chars | Mais longa, fácil de copiar, case-insensitive |
| base62 | 22 chars | 43 chars | Apenas alfanumérica, sem símbolos |
Independentemente da sua escolha, mantenha o secret case-sensitive e nunca o converta para letras minúsculas antes da comparação. Um bug comum é um middleware ou proxy que normaliza os valores dos headers, quebrando silenciosamente chaves que contenham letras maiúsculas. Documente o formato exato e torne o prefixo distinto o suficiente para que uma chave seja reconhecível em um ticket de suporte, sem ser útil para um atacante.
Não torne a chave autodescritiva além do prefixo. Incorporar o ID do usuário, um checksum ou uma data de expiração dentro da chave pode tentar induzi-lo a pular a consulta ao banco de dados, mas isso também significa que a chave carrega informações e, de qualquer forma, não poderá ser revogada sem uma consulta. Mantenha o secret opaco e deixe que o banco de dados detenha o significado.
Armazenando secrets do cliente com segurança
O armazenamento no lado do servidor é apenas metade da história; o cliente também precisa manter a chave segura. Forneça orientações claras e assertivas para os integradores, pois o comportamento padrão de muitos desenvolvedores é colar uma chave em um arquivo e fazer o commit.
Recomende variáveis de ambiente para servidores, injetadas pela plataforma ou por um gerenciador de secrets, em vez de escritas em um .env versionado. No CI, use o armazenamento de secrets da pipeline e mascare o valor nos logs. Para desenvolvimento local, carregue de um .env que esteja no git-ignore, e prefira chaves sk_test_ para que qualquer erro afete apenas dados de sandbox.
# Load from the environment, never hard-code.
export MYAPP_API_KEY="sk_live_8f2a9c..."
curl -H "Authorization: Bearer $MYAPP_API_KEY" https://api.example.com/v1/projects
Direcione os usuários para gerenciadores de secrets — AWS Secrets Manager, Google Secret Manager, Vault, ou as variáveis integradas da plataforma — e explique a rotação de chaves de forma prática. Se o seu SDK ler a chave de uma variável de ambiente por padrão, a maioria das integrações fará a coisa certa sem precisar de instruções, o que é o melhor tipo de controle de segurança.
Chaves secret e chaves publishable
Muitas plataformas fornecem dois tipos de chaves, e confundi-las causa problemas reais. Uma secret key autentica um servidor confiável e nunca deve ser exposta. Uma publishable key foi feita para ser incorporada ao código do cliente e é seguro que seja pública, pois não concede privilégios por si só.
sk_live_... secret, server-only, carries scopes and a quota
pk_live_... publishable, client-safe, identifies the account only
Uma publishable key é útil para atribuição e rate limiting no navegador, mas cada operação privilegiada ainda deve ser autorizada por algo que o cliente não possa falsificar: um token de curta duração emitido pelo seu servidor ou uma sessão de usuário. Trate a publishable key como um identificador, não como uma credencial, e nunca permita que a presença dela sozinha conceda acesso.
Nomear as duas de forma consistente torna a distinção óbvia à primeira vista e permite que scanners de segredos, code review e documentação reforcem a mesma regra. Se um desenvolvedor colar uma chave sk_ no código do front-end, apenas o prefixo já deve tornar o erro visível antes do deploy.
Melhores práticas
- Gere o secret a partir de um CSPRNG com pelo menos 128 bits de entropia, com um prefixo para facilitar a leitura e a identificação do ambiente.
- Armazene apenas um hash SHA-256 e um prefixo curto; nunca persista a chave completa.
- Exiba a chave completa exatamente uma vez, no momento da criação, e utilize a rotação como caminho de recuperação.
- Compare hashes em tempo constante e retorne o mesmo erro para chaves desconhecidas e inválidas.
- Indexe o prefixo para que a verificação exija apenas uma busca e um hash.
- Defina o escopo de cada chave com as permissões mínimas e anexe um rate limit por chave.
- Codifique o ambiente no prefixo e rejeite chaves de sandbox na API de produção.
- Aceite chaves no header
Authorization, nunca em uma query string. - Suporte a rotação com zero-downtime através de uma janela de sobreposição, e torne a revogação imediata.
- Monitore
last_used_at, gere alertas para anomalias e notifique os proprietários a cada alteração de credencial. - Separe secret keys de publishable keys, e nunca permita que uma publishable key conceda acesso por conta própria.
- Documente o formato da chave, os escopos e a política de rotação para que os integradores possam segui-la sem precisar adivinhar.
Erros comuns
- Armazenar chaves em texto simples e assumir que o banco de dados nunca terá um vazamento.
- Fazer o hash de cada linha para encontrar uma correspondência em vez de indexar um prefixo.
- Usar
Math.randomou um UUID como segredo e reduzir o espaço de busca. - Embutir uma chave de longa duração em um bundle de navegador, app mobile ou binário de desktop.
- Colocar a chave em uma query string, onde logs e histórico a capturam.
- Conceder acesso total a cada chave porque definir escopos parecia trabalho extra.
- Nunca rotacionar ou expirar chaves, fazendo com que um vazamento permaneça útil indefinidamente.
- Excluir linhas revogadas e perder a trilha de auditoria.
- Registrar headers ou URLs completos em logs, vazando chaves para ferramentas de observabilidade.
- Retornar um erro diferente para um prefixo desconhecido do que para um segredo incorreto.
Próximos passos
As API keys são a credencial mais simples do seu arsenal, e os mesmos instintos — hash em repouso, escopo restrito, rotação sob demanda — aplicam-se a tudo. Se você precisa de tokens verificáveis de curta duração que carreguem claims, leia o guia de JWT. Quando o chamador age em nome de um usuário e precisa de consentimento, o OAuth 2.0 é o modelo correto. As strings de permissão em uma chave são os mesmos átomos usados por RBAC, portanto, aquele guia é o companheiro natural para a definição de escopos. E como as chaves são um agrupador natural para cotas, o guia de rate limiting mostra como evitar que uma única integração esgote a capacidade para todos os demais.