# Autenticación

> Cómo firmar cada solicitud con HMAC-SHA256 en el esquema SB1-HMAC-SHA256, con valores de validación y código en Node.js y Python.

URL canónica: https://apis.cortecloud.com.br/docs/es/comecando/autenticacao/

Cada solicitud a la API se firma con HMAC-SHA256 en el esquema `SB1-HMAC-SHA256`. No hay login, sesión, cookie ni token que renovar: la misma secret key firma todas las llamadas hasta que se reemplace.

## Credenciales {#credenciais}

Serrabits le proporciona a su integración dos credenciales por entorno. Para obtenerlas, escriba a suporte@serrabits.com.br.

| Credencial | Uso |
| --- | --- |
| **api key** | Identifica su integración. Va en el encabezado `Authorization` de cada solicitud. |
| **secret key** | Genera la firma de cada solicitud. Nunca se envía: solo la firma va en la solicitud. |

La secret key debe quedar únicamente en su backend. No la coloque en código de frontend, en una app móvil ni en ningún lugar accesible desde el navegador del usuario: quien tenga la secret key puede hacer llamadas en nombre de su integración. Si se filtra, escriba a suporte@serrabits.com.br para revocarla y recibir una nueva.

## Encabezados {#cabecalhos}

Cada solicitud lleva dos encabezados:

```text
Authorization: SB1-HMAC-SHA256 api-key="<api key>", signed-headers="host", signature="<firma>"
x-company-internal-code: <código de la central>
```

- `Authorization` identifica su integración y lleva la firma de la solicitud, calculada como se describe más abajo.
- `x-company-internal-code` indica la central sobre la que actúa la solicitud (ver [código de la central](https://apis.cortecloud.com.br/docs/es/comecando/conceitos.md#codigo-da-central)). Este encabezado no entra en la firma.

Las solicitudes con cuerpo también envían `Content-Type: application/json`.

La firma cubre el método, el path, la query string, el host y el cuerpo. Alterar cualquiera de ellos después de firmar invalida la solicitud, y una solicitud capturada no puede reutilizarse con otro path, método o cuerpo.

## Cómo calcular la firma {#como-calcular-a-assinatura}

1. Arme el texto firmado, con un dato por línea, separados por `\n` (salto de línea real, no los caracteres `\` y `n`):
   1. método HTTP en mayúsculas (`GET`, `POST`, `PUT`, `PATCH`);
   2. path de la URL, sin host ni query string (`/materials/boards`); un path vacío vale `/`;
   3. parámetros de la query string ordenados por nombre (y, en nombres repetidos, por valor), cada uno como `nombre=valor` codificado como en el `encodeURIComponent` de JavaScript, unidos por `&`; sin query string, línea vacía;
   4. `host:<host>` seguido de una línea en blanco, donde `<host>` es exactamente el valor que su biblioteca HTTP envía en el encabezado `host` (el hostname, más `:puerto` solo cuando el puerto no es el predeterminado del protocolo);
   5. la palabra `host`, que es la lista de encabezados firmados;
   6. SHA-256 en hexadecimal de los bytes del cuerpo; sin cuerpo, el SHA-256 de la cadena vacía (`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`).
2. Calcule el HMAC-SHA256 de ese texto con la secret key como clave.
3. Use el resultado en hexadecimal en minúsculas como `<firma>`.

La línea en blanco después de `host:<host>` forma parte del texto: la línea del host termina con su propio salto de línea, sumado al `\n` que separa las partes.

### Ejemplo: GET {#exemplo-get}

Texto firmado para `GET /materials/boards?limit=10` en el host `api.example.com`, sin cuerpo:

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

host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
```

### Ejemplo: POST {#exemplo-post}

Texto firmado para `POST /services/123/production` en el host `api.example.com`, con el cuerpo `{"sellerEmail":"vendedor@example.com"}`. El hash se calcula sobre los bytes exactos del cuerpo enviado: reformatear el JSON después de firmar (espacios, saltos de línea, orden de las claves) invalida la firma.

```text
POST
/services/123/production

host:api.example.com

host
d4f22123a7a0685bd98bb9f7fc9b3db455e5b4e0c4420f0e0ddedd4826a3e0cc
```

Sin query string, la tercera línea queda vacía.

### Valide su implementación {#valide-a-sua-implementacao}

Con la secret key de prueba `sb_sk_exemplo`, los dos textos anteriores producen estas firmas:

| Ejemplo | Firma |
| --- | --- |
| GET | `e2a240cea084ff4950cc78e1d48d6da563aae9f7935a8dfaa28c3672598ef344` |
| POST | `8ac32656f8233168640afaff2f7c6213e42dd0fb9cdbd3f81b2ae666cab94e2b` |

Si su implementación llega a los mismos valores, está armando el texto como la API lo espera.

## Código de referencia {#codigo-de-referencia}

### Node.js {#nodejs}

```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 {#python}

```python
# urllib.parse.quote con safe="!*'()" reproduce el mismo conjunto
# de caracteres que el encodeURIComponent de JavaScript deja sin 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}"'
```

No defina el encabezado `host` manualmente en la llamada HTTP: la biblioteca ya lo envía a partir de la URL. Solo asegúrese de que el valor usado para firmar (`url.host` y `host` en los ejemplos) sea exactamente el que ella envía; en la mayoría de las bibliotecas, es el mismo.

En las solicitudes con cuerpo, firme y envíe los mismos bytes. Serialice el JSON una sola vez, guarde la cadena y úsela tanto en el hash como en el cuerpo de la solicitud.

## Cuando la autenticación falla {#quando-a-autenticacao-falha}

La API responde `401` cuando:

- `Authorization` o `x-company-internal-code` falta o está mal formado;
- la api key es desconocida o fue revocada;
- la central indicada en `x-company-internal-code` no está vinculada a la api key o no está activa en Cortecloud;
- `signed-headers` nombra un encabezado que la solicitud no envió;
- la firma no coincide con la solicitud recibida.

Para diagnosticar una firma que no coincide, compare el texto que usted firmó con el formato anterior, línea por línea: las causas más comunes son la falta de la línea en blanco después del host, la query string desordenada y el cuerpo reformateado entre el hash y el envío.
