Pular para o conteúdo principal

Autenticação

Toda requisição à API é assinada com HMAC-SHA256 no esquema SB1-HMAC-SHA256. Não há login, sessão, cookie nem token para renovar: a mesma secret key assina todas as chamadas até ser trocada.

Credenciais​

A Serrabits fornece à sua integração duas credenciais por ambiente. Para obtê-las, escreva para suporte@serrabits.com.br.

CredencialUso
api keyIdentifica a sua integração. Vai no cabeçalho Authorization de toda requisição.
secret keyGera a assinatura de cada requisição. Nunca é enviada: só a assinatura vai na requisição.

A secret key deve ficar apenas no seu backend. Não a coloque em código de frontend, app mobile ou qualquer lugar acessível pelo navegador do usuário: quem tiver a secret key consegue fazer chamadas em nome da sua integração. Se ela vazar, escreva para suporte@serrabits.com.br para revogá-la e receber uma nova.

Cabeçalhos​

Toda requisição leva dois cabeçalhos:

Authorization: SB1-HMAC-SHA256 api-key="<api key>", signed-headers="host", signature="<assinatura>"
x-company-internal-code: <código da central>
  • Authorization identifica a sua integração e traz a assinatura da requisição, calculada como descrito abaixo.
  • x-company-internal-code indica a central sobre a qual a requisição atua (ver código da central). Esse cabeçalho não entra na assinatura.

Requisições com corpo também enviam Content-Type: application/json.

A assinatura cobre método, caminho, query string, host e corpo. Alterar qualquer um deles depois de assinar invalida a requisição, e uma requisição capturada não pode ser reaproveitada com outro caminho, método ou corpo.

Como calcular a assinatura​

  1. Monte o texto a ser assinado, com uma informação por linha, separadas por \n (quebra de linha real, não os caracteres \ e n):
    1. método HTTP em maiúsculas (GET, POST, PUT, PATCH);
    2. caminho da URL, sem host nem query string (/materials/boards); caminho vazio vale /;
    3. parâmetros da query string ordenados por nome (e, em nomes repetidos, por valor), cada um como nome=valor codificado como no encodeURIComponent do JavaScript, unidos por &; sem query string, linha vazia;
    4. host:<host> seguido de uma linha em branco, onde <host> é exatamente o valor que a sua biblioteca HTTP envia no cabeçalho host (o hostname, mais :porta só quando a porta não é a padrão do protocolo);
    5. a palavra host, que é a lista de cabeçalhos assinados;
    6. SHA-256 em hexadecimal dos bytes do corpo; sem corpo, o SHA-256 da string vazia (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855).
  2. Calcule o HMAC-SHA256 desse texto com a secret key como chave.
  3. Use o resultado em hexadecimal minúsculo como <assinatura>.

A linha em branco depois de host:<host> faz parte do texto: a linha do host termina com a sua própria quebra de linha, somada ao \n que separa as partes.

Exemplo: GET​

Texto assinado para GET /materials/boards?limit=10 no host api.example.com, sem corpo:

GET
/materials/boards
limit=10
host:api.example.com

host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Exemplo: POST​

Texto assinado para POST /services/123/production no host api.example.com, com o corpo {"sellerEmail":"vendedor@example.com"}. O hash é calculado sobre os bytes exatos do corpo enviado: reformatar o JSON depois de assinar (espaços, quebras de linha, ordem das chaves) invalida a assinatura.

POST
/services/123/production

host:api.example.com

host
d4f22123a7a0685bd98bb9f7fc9b3db455e5b4e0c4420f0e0ddedd4826a3e0cc

Sem query string, a terceira linha fica vazia.

Valide a sua implementação​

Com a secret key de teste sb_sk_exemplo, os dois textos acima produzem estas assinaturas:

ExemploAssinatura
GETe2a240cea084ff4950cc78e1d48d6da563aae9f7935a8dfaa28c3672598ef344
POST8ac32656f8233168640afaff2f7c6213e42dd0fb9cdbd3f81b2ae666cab94e2b

Se a sua implementação chega nos mesmos valores, ela está montando o texto do jeito que a API espera.

Código de referência​

Node.js​

const crypto = require('crypto');

function canonicalQueryString(url) {
const entries = [...url.searchParams.entries()].sort(([ak, av], [bk, bv]) =>
ak === bk ? av.localeCompare(bv) : ak.localeCompare(bk),
);
return entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
}

function buildCanonicalRequest(method, url, body = '') {
const payloadHash = crypto.createHash('sha256').update(body, 'utf-8').digest('hex');
return [
method.toUpperCase(),
url.pathname || '/',
canonicalQueryString(url),
`host:${url.host}\n`,
'host',
payloadHash,
].join('\n');
}

const url = new URL('https://api.example.com/materials/boards?limit=10');
const canonicalRequest = buildCanonicalRequest('GET', url);
const signature = crypto.createHmac('sha256', secretKey).update(canonicalRequest).digest('hex');

const authorization = `SB1-HMAC-SHA256 api-key="${apiKey}", signed-headers="host", signature="${signature}"`;

Python​

# urllib.parse.quote com safe="!*'()" reproduz o mesmo conjunto
# de caracteres que o encodeURIComponent do JavaScript deixa sem escapar.
import hashlib
import hmac
import urllib.parse


def encode(value):
return urllib.parse.quote(str(value), safe="!*'()")


def canonical_query_string(query_pairs):
return "&".join(
f"{encode(k)}={encode(v)}"
for k, v in sorted(query_pairs, key=lambda kv: (kv[0], kv[1]))
)


def build_canonical_request(method, path, query_pairs, host, body=b""):
payload_hash = hashlib.sha256(body).hexdigest()
return "\n".join([
method.upper(),
path or "/",
canonical_query_string(query_pairs),
f"host:{host}\n",
"host",
payload_hash,
])


canonical_request = build_canonical_request("GET", "/materials/boards", [("limit", "10")], "api.example.com")
signature = hmac.new(secret_key.encode(), canonical_request.encode(), hashlib.sha256).hexdigest()

authorization = f'SB1-HMAC-SHA256 api-key="{api_key}", signed-headers="host", signature="{signature}"'

Não defina o cabeçalho host manualmente na chamada HTTP: a biblioteca já o envia a partir da URL. Garanta só que o valor usado para assinar (url.host e host nos exemplos) seja exatamente o que ela envia; na maioria das bibliotecas, é o mesmo.

Para requisições com corpo, assine e envie os mesmos bytes. Serialize o JSON uma vez, guarde a string e use-a tanto no hash quanto no corpo da requisição.

Quando a autenticação falha​

A API responde 401 quando:

  • Authorization ou x-company-internal-code falta ou está malformado;
  • a api key é desconhecida ou foi revogada;
  • a central indicada em x-company-internal-code não está vinculada à api key ou não está ativa no Cortecloud;
  • signed-headers nomeia um cabeçalho que a requisição não enviou;
  • a assinatura não confere com a requisição recebida.

Para diagnosticar uma assinatura que não confere, compare o texto que você assinou com o formato acima, linha a linha: as causas mais comuns são a falta da linha em branco depois do host, a query string fora de ordem e o corpo reformatado entre o hash e o envio.