Authentification (HMAC)
Tous les appels backend → relais sont signés avec le même schéma HMAC (parfois appelé « HMAC#1 »), identique pour l’Auth Relay et le Chat Relay. Apprenez-le ici ; les pages produit ne font qu’y renvoyer.
Quel mécanisme pour quel appel ?
Section intitulée « Quel mécanisme pour quel appel ? »| Surface | Authentification |
|---|---|
Endpoints */relay/* (backend → relais) | Signature HMAC (cette page) |
Endpoints d’administration (/provision/*, /rotate/*, /fetch/*…) | En-tête X-Admin-Key |
Endpoints d’appareil de l’Auth Relay (/device/*) | Authorization: Bearer <jeton> |
Console du Chat Relay (/console/*) | Authorization: Bearer <console_token> |
| Callbacks (relais → votre backend) | Même signature HMAC, que vous vérifiez — voir Webhooks & callbacks |
Les trois en-têtes
Section intitulée « Les trois en-têtes »Chaque requête signée porte :
| En-tête | Valeur |
|---|---|
X-Bloonio-Tenant-Id | Votre tenant_id. |
X-Bloonio-Timestamp | L’horodatage Unix courant en millisecondes. |
X-Bloonio-Signature | hex( hmac_sha256( tenant_secret, "{timestamp_ms}.{sha256_hex(body)}" ) ) |
L’algorithme de signature
Section intitulée « L’algorithme de signature »- Sérialisez le corps de la requête exactement tel qu’il sera envoyé
(mêmes octets). Pour une requête sans corps (
GET), utilisez une chaîne vide. - Calculez
body_hash = sha256_hex(body). - Relevez
timestamp_ms, l’heure Unix courante en millisecondes. - Construisez la chaîne à signer :
"{timestamp_ms}.{body_hash}". - Calculez
signature = hmac_sha256(tenant_secret, chaîne_à_signer)en hexadécimal. - Envoyez les trois en-têtes ci-dessus avec la requête.
Exemple d’implémentation
Section intitulée « Exemple d’implémentation »import hashlibimport hmacimport jsonimport time
import httpx
TENANT_ID = "tnt_..." # depuis le provisioningTENANT_SECRET = "sk_..." # secret, côté serveur uniquementBASE_URL = "$BASE_URL" # ex. https://auth-relay.bloonio.com
def signed_headers(tenant_id: str, tenant_secret: str, body: bytes) -> dict: ts_ms = str(int(time.time() * 1000)) body_hash = hashlib.sha256(body).hexdigest() signing_string = f"{ts_ms}.{body_hash}" signature = hmac.new( tenant_secret.encode(), signing_string.encode(), hashlib.sha256, ).hexdigest() return { "X-Bloonio-Tenant-Id": tenant_id, "X-Bloonio-Timestamp": ts_ms, "X-Bloonio-Signature": signature, "Content-Type": "application/json", }
payload = {"example": "value"}body = json.dumps(payload).encode() # ces octets exacts sont signés ET envoyés
resp = httpx.post( f"{BASE_URL}/api/v1/relay/...", headers=signed_headers(TENANT_ID, TENANT_SECRET, body), content=body, # on envoie `content`, pas `json=`, pour figer les octets timeout=10,)resp.raise_for_status()import crypto from "node:crypto";
const TENANT_ID = "tnt_...";const TENANT_SECRET = "sk_...";const BASE_URL = "$BASE_URL"; // ex. https://chat-relay.bloonio.com
function signedHeaders(tenantId, tenantSecret, body) { const tsMs = String(Date.now()); const bodyHash = crypto.createHash("sha256").update(body).digest("hex"); const signingString = `${tsMs}.${bodyHash}`; const signature = crypto .createHmac("sha256", tenantSecret) .update(signingString) .digest("hex"); return { "X-Bloonio-Tenant-Id": tenantId, "X-Bloonio-Timestamp": tsMs, "X-Bloonio-Signature": signature, "Content-Type": "application/json", };}
const body = JSON.stringify({ example: "value" }); // signé ET envoyé tel quel
const res = await fetch(`${BASE_URL}/api/v1/relay/...`, { method: "POST", headers: signedHeaders(TENANT_ID, TENANT_SECRET, body), body,});if (!res.ok) throw new Error(`Relais : ${res.status}`);Ce que le relais vérifie
Section intitulée « Ce que le relais vérifie »À réception, le relais :
- exige la présence des trois en-têtes ;
- vérifie que
X-Bloonio-Timestampest dans une fenêtre d’environ 30 secondes (anti-rejeu temporel) ; - vérifie que le
tenant_idexiste et que le tenant est actif (sinon403) ; - rejette toute signature déjà vue (protection anti-rejeu, sur une courte fenêtre configurable) ;
- recalcule la signature et la compare en temps constant.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »| Symptôme | Cause probable |
|---|---|
401 signature invalide | Le corps signé diffère du corps envoyé, ou mauvais tenant_secret. |
401 horodatage hors fenêtre | Horloge décalée, ou requête rejouée trop tard. Synchronisez via NTP. |
403 tenant inactif | Tenant suspendu — voir Cycle de vie d’un tenant. |
401 rejeu détecté | La même signature a déjà été utilisée ; générez un nouvel horodatage. |
Étapes suivantes
Section intitulée « Étapes suivantes »- Cycle de vie d’un tenant — d’où viennent
tenant_idettenant_secret. - Webhooks & callbacks — vérifier la même signature dans l’autre sens.