Pular para o conteúdo principal

Autenticação

Cada chamada é assinada. Não há token de sessão, não há login: a credencial é a chave, e a prova de que ela é sua é a assinatura.

Os quatro cabeçalhos​

CabeçalhoConteúdo
X-Api-Keyo clientId da credencial
X-Timestampepoch em segundos
X-Nonceúnico por chamada, até 128 caracteres
X-SignatureHMAC-SHA256 da cadeia canônica, em hexadecimal minúsculo

Sempre os quatro. Falta de qualquer um responde 401.

A cadeia canônica​

Cinco partes, concatenadas com \n:

MÉTODO EM MAIÚSCULAS
/caminho/da/requisicao
timestamp
nonce
sha256(corpo)

O corpo entra como resumo, não inteiro. Corpo vazio vira sha256("") — que é sempre e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

O host fica de fora. Se você fala com mais de um domínio, a mesma assinatura vale nos dois, e você não precisa reassinar por domínio.

Por que cada parte está lá​

Não é cerimônia. A ausência de cada campo abre um ataque concreto:

Sem…O que passa a ser possível
o métodoum GET assinado é reenviado como DELETE
o caminhouma cobrança de R$ 10 é reenviada contra outro endpoint
o instantea mesma chamada vale para sempre
o nonceela vale dentro da janela de tolerância
o corpotroca-se o valor mantendo a assinatura

Código​

function assinar(string $metodo, string $caminho, string $corpo, string $secret): array
{
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));

$cadeia = implode("\n", [
strtoupper($metodo),
$caminho,
$ts,
$nonce,
hash('sha256', $corpo),
]);

return [
'X-Timestamp' => $ts,
'X-Nonce' => $nonce,
'X-Signature' => hash_hmac('sha256', $cadeia, $secret),
];
}

:::warning Assine os bytes que você vai enviar Serialize o JSON uma vez, assine essa string, e mande essa mesma string no corpo. Reserializar depois de assinar muda os bytes — a ordem das chaves, um espaço — e a assinatura deixa de conferir por um motivo que não aparece em lugar nenhum. :::

Tolerância e nonce​

300 segundos entre X-Timestamp e o relógio do servidor. Fora disso, 401.

Sincronize o relógio do seu servidor. Essa é a causa número um de invalid_signature numa integração que já funcionou por meses.

O nonce é consumido: repeti-lo dentro da janela responde 401, mesmo com assinatura correta. Use um valor aleatório por chamada, não um contador que você reinicia no deploy.

Todos os erros de autenticação respondem igual​

Chave desconhecida, assinatura errada, cabeçalho ausente, instante fora da janela e nonce repetido devolvem todos 401 com error.code: "invalid_signature".

São deliberadamente indistinguíveis. Diferenciá-los transformaria o endpoint num verificador de quais chaves existem.

Isso significa que a depuração é do seu lado, e vale seguir esta ordem:

  1. o relógio do servidor está sincronizado (NTP)?
  2. o corpo assinado é byte a byte o corpo enviado?
  3. o caminho na cadeia é só o caminho — sem host, com a barra inicial?
  4. o nonce é novo?
  5. a chave é do ambiente certo?

Guarde o segredo como segredo​

O clientSecret foi mostrado uma única vez, na criação. Não existe "ver de novo": se você o perdeu, revogue a credencial e crie outra.

E ele nunca sai do seu servidor. Uma chave no front é uma chave publicada — esta API não é para o navegador.