API Security

API Keys

Uma API key é uma credencial de longa duração para máquinas. Emita-a apenas uma vez, armazene apenas o hash, defina escopos restritos e crie um mecanismo para rotacioná-la e revogá-la antes que isso se torne necessário.

intermediate14 min readUpdated 16 de set. de 2026
keys.ts
ts
// keys.ts
import crypto from "node:crypto";

export function generateApiKey(env: "live" | "test") {
  const secret = crypto.randomBytes(32).toString("base64url");
  const prefix = `sk_${env}_`;
  const key = `${prefix}${secret}`;

  return {
    key,                        // returned to the user once
    prefix: key.slice(0, 12),   // stored and indexed for lookup
    hash: hashKey(key),         // stored instead of the key
  };
}

export function hashKey(key: string): string {
  return crypto.createHash("sha256").update(key).digest("hex");
}

export function timingSafeEqual(a: string, b: string): boolean {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}
Usado para
APIs públicas e comunicação server-to-server
Formato
Prefixo mais um segredo aleatório
Armazenado como
Hash SHA-256
Exibido
Uma única vez, na criação
Transporte
Header Authorization
Escopo
Permissões e rate limits
Rotação
Cria a nova chave e depois revoga a antiga
Risco de vazamento
Histórico do Git, logs, código do cliente

Por que importa

O que um bom sistema de chaves oferece

Uma credencial, muitos serviços

Uma chave autentica uma máquina sem a necessidade de um fluxo de login. É simples de emitir, enviar e rotacionar, e é por isso que toda developer platform as utiliza.

Hashed em repouso

Armazene apenas o hash, exatamente como faria com uma senha. Assim, um dump de banco de dados roubado não fornecerá nada que um atacante possa enviar para sua API.

Com escopo e rate limit

Cada chave possui suas próprias permissões e cotas, portanto, uma chave comprometida só poderá fazer o que lhe foi permitido e na velocidade autorizada.

O panorama completo

Três propriedades de uma chave segura

Um segredo aleatório que não pode ser adivinhado, um hash em repouso que não pode ser replicado a partir de um dump, e um escopo que limita o dano em caso de vazamento.

Segredo

Identificar

Uma string aleatória de alta entropia é a própria credencial. Sua única função é ser impossível de adivinhar e única para cada chamador.

Escopo

Limitar

Uma chave carrega um conjunto de permissões. O verificador checa a ação contra o escopo antes que o handler seja executado, exatamente como as roles de um usuário.

Cota

Proteger

As chaves criam um agrupamento natural para rate limits, evitando que uma única integração instável esgote a capacidade para todos os outros.

HTML5 de uma olhada

As peças que você irá construir

Formato

Um prefixo legível mais um segredo aleatório longo, ex: sk_live_8f2a...

Hashing

Armazene um hash SHA-256; compare utilizando uma função de tempo constante.

Escopos

Permissões granulares como projects:read e deploy:write.

Rate limits

Anexe uma cota a cada chave e a aplique individualmente.

Rotação

Emita uma substituta, migre o tráfego e então revogue a chave antiga.

Monitoramento

Rastreie o last_used_at e gere alertas sobre mudanças súbitas de comportamento.

Modelo de dados

A tabela api_keys

Apenas o hash e um prefixo curto são armazenados. A chave completa existe apenas uma vez, na resposta que a criou, e nunca poderá ser recuperada.

A tabela api_keysTabela PostgreSQL
  • idbigserialChave primária substituta
  • nametextRótulo humano, como 'CI deploy' ou 'app mobile'
  • prefixtextCaracteres iniciais da chave, indexados para busca rápida
  • key_hashtextSHA-256 da chave completa, nunca a chave em si
  • scopestext[]Permissões que a chave pode exercer
  • owner_idbigintO usuário ou serviço que criou a chave
  • last_used_attimestamptzAtualizado no uso para detecção de anomalias e limpeza
  • revoked_attimestamptzDefinido quando a chave é revogada; null significa ativa

Apenas o hash e um prefixo curto são armazenados. A chave completa existe apenas uma vez, na resposta que a criou, e nunca poderá ser recuperada.

Fluxo

Emitindo e verificando uma chave

A chave completa existe em exatamente uma resposta; tudo depois disso funciona com base em um hash e um prefixo.

  1. 1

    Gerar um segredo aleatório

    Extraia pelo menos 128 bits de um CSPRNG e combine-os com um prefixo legível.

  2. 2

    Exibi-la uma única vez

    Retorne a chave completa na resposta de criação e nunca a armazene ou exiba novamente.

  3. 3

    Armazenar o hash e o prefixo

    Persista o hash, o prefixo curto, os escopos e o proprietário na tabela api_keys.

  4. 4

    Enviá-la em um header

    O cliente apresenta a chave no header Authorization ou em um header dedicado em cada requisição.

  5. 5

    Fazer o hash e comparar

    Busque a chave pelo prefixo, faça o hash do valor apresentado e compare em tempo constante.

  6. 6

    Anexar os escopos

    Carregue as permissões e a cota da chave na requisição para que o handler possa validá-las.

  7. 7

    Rotacionar ou revogar

    Emita uma substituta, migre o tráfego e marque a chave antiga como revogada.

O guia completo

API Keys: Tudo que voce precisa saber

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.

The shape of an API key
sk_live_8f2a9c1d4e6b7a0f3c5d8e2b1a4f7c9d6e3b0a8f5c2d1e4b7a9c6f0d3e8b1a4
prefixenvironment and type, safe to log and scan for
secret256 bits of cryptographically secure randomness

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.random ou 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.

Na pratica

Criar, verificar, definir escopo, revogar

As quatro operações que todo sistema de API keys precisa, e nada mais.

routes/keys.ts
import crypto from "node:crypto";

router.post("/keys", requireAuth(), async (req, res) => {
  const { name, scopes } = req.body;
  const secret = crypto.randomBytes(32).toString("base64url");
  const key = `sk_live_${secret}`;

  const [row] = await db.apiKey.insert({
    name,
    ownerId: req.user.id,
    prefix: key.slice(0, 12),
    keyHash: crypto.createHash("sha256").update(key).digest("hex"),
    scopes,
  });

  // The only time the full key is ever returned.
  res.status(201).json({
    id: row.id,
    name: row.name,
    prefix: row.prefix,
    key,
  });
});

Armazene o hash, não a chave

Um hash é suficiente para verificar uma chave apresentada e inútil para quem roubar a tabela. É o mesmo raciocínio aplicado a senhas.

Preferível
CREATE TABLE api_keys (
  id         bigserial PRIMARY KEY,
  prefix     text NOT NULL,
  key_hash   text NOT NULL,
  scopes     text[] NOT NULL DEFAULT '{}',
  revoked_at timestamptz
);

CREATE INDEX api_keys_prefix_idx ON api_keys (prefix);
Evitar
CREATE TABLE api_keys (
  id  bigserial PRIMARY KEY,
  key text NOT NULL
  -- one database dump or backup leak
  -- hands over every customer's key
);

Envie chaves em um header

Headers não são logados por padrão, não aparecem no histórico do navegador e não vazam através do header Referer quando uma página linka para outro lugar.

Preferível
GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...
Evitar
GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1
Host: api.example.com
# query strings land in access logs, proxies
# and browser history

Trade-offs

API keys são a credencial certa?

Chaves são simples e universais, o que é tanto sua força quanto sua fraqueza. Entenda o que você abre mão.

Strengths

  • Extremamente simples para clientes

    Não há fluxo de login, nem refresh token ou problemas de dessincronização de relógio. O cliente armazena uma string e a envia, por isso toda CLI e developer platform usa chaves.

  • Revogáveis por integração

    Cada chave é uma credencial nomeada. Você pode revogar a chave usada por um script vazado sem afetar qualquer outro cliente ou serviço.

  • Fácil de definir escopo e medição

    Uma chave é uma unidade natural para permissões e rate limits, permitindo dar a uma integração acesso apenas de leitura e uma cota modesta sem precisar construir nada novo.

Trade-offs

  • Longa duração por natureza

    Chaves geralmente não expiram, então uma chave vazada é perigosa até que alguém perceba. Expirações curtas, rotação e monitoramento reduzem essa janela.

  • Sem contexto de usuário

    Uma chave identifica um serviço, não uma pessoa. Qualquer coisa que exija consentimento, acesso delegado ou auditoria por usuário deve usar OAuth.

  • Difícil de manter em segredo no cliente

    Uma chave embutida em um app mobile ou em um bundle de navegador é pública. Use um proxy de backend ou um token de curta duração para esses clientes.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender API Key Management?

Nosso tutorial interativo te guia por API Key Management passo a passo — com quizzes e codigo real que voce pode executar no navegador.