FONDAMENTIAutenticazione delle richieste
Ogni richiesta all'API è firmata. La firma dimostra il possesso del segreto del sito senza trasmetterlo e vincola la richiesta all'istante in cui è stata prodotta.
Header | Contenuto |
|---|
WebsiteId
| Identificativo del sito, in formato GUID |
WebSecretId
| Identificativo pubblico della chiave associata al sito |
X-Timestamp
| Istante della richiesta, in secondi Unix UTC |
X-Website-Auth
| Firma della richiesta, in Base64 |
WebsiteId e WebSecretId si leggono nel pannello, nelle impostazioni del sito. Il segreto usato per firmare non compare in nessun header e non lascia il server chiamante.
Come si calcola la firma
Il messaggio da firmare è la concatenazione dell'identificativo della chiave e del timestamp, separati da una barra verticale: {webSecretId}|{timestamp}.
Il messaggio viene sottoposto a HMAC-SHA256 usando il segreto del sito come chiave, entrambi codificati in UTF-8. Il risultato binario si trasmette in Base64 nell'header X-Website-Auth.
Lo stesso timestamp usato nel calcolo va inviato in X-Timestamp: un timestamp diverso da quello firmato produce una firma non valida.
import crypto from "node:crypto";
const websiteId = "00000000-0000-0000-0000-000000000000";
const webSecretId = "IDENTIFICATIVO_CHIAVE";
const secretKey = process.env.GAJACMS_SECRET;
const timestamp = Math.floor(Date.now() / 1000);
const signature = crypto
.createHmac("sha256", secretKey)
.update(`${webSecretId}|${timestamp}`, "utf8")
.digest("base64");
const response = await fetch("https://esempio.it/api/v1/content/pages/resolve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"WebsiteId": websiteId,
"WebSecretId": webSecretId,
"X-Timestamp": String(timestamp),
"X-Website-Auth": signature
},
body: JSON.stringify({ requestedUrl: "/chi-siamo" })
});var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var message = $"{webSecretId}|{timestamp}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(message)));
var request = new HttpRequestMessage(HttpMethod.Post, "https://esempio.it/api/v1/content/pages/resolve");
request.Headers.Add("WebsiteId", websiteId.ToString());
request.Headers.Add("WebSecretId", webSecretId);
request.Headers.Add("X-Timestamp", timestamp.ToString());
request.Headers.Add("X-Website-Auth", signature);
request.Content = JsonContent.Create(new { requestedUrl = "/chi-siamo" });TIMESTAMP=$(date -u +%s)
SIGNATURE=$(printf '%s|%s' "$WEB_SECRET_ID" "$TIMESTAMP" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" -binary \
| base64)
curl -X POST "https://esempio.it/api/v1/content/pages/resolve" \
-H "Content-Type: application/json" \
-H "WebsiteId: $WEBSITE_ID" \
-H "WebSecretId: $WEB_SECRET_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Website-Auth: $SIGNATURE" \
-d '{"requestedUrl":"/chi-siamo"}'
Finestra di validità
La differenza fra il timestamp inviato e l'ora del server non può superare i 120 secondi, in anticipo o in ritardo. Oltre quella soglia la richiesta viene rifiutata anche con una firma corretta.
La firma va quindi calcolata a ogni richiesta e non può essere memorizzata e riutilizzata. L'orologio del sistema chiamante deve essere sincronizzato.
Esito del controllo
Il controllo avviene prima di qualunque logica applicativa. Quando fallisce, la risposta è 403 con corpo di testo Forbidden: non è un JSON e non adotta il wrapper descritto nella pagina Formato delle risposte.
Le cause del rifiuto sono:
uno degli header richiesti è assente;
WebsiteId non è un GUID valido;
X-Timestamp non è un numero intero;
la differenza dall'ora del server supera i 120 secondi;
WebSecretId non corrisponde a nessuna chiave registrata;
la firma non coincide con quella calcolata dal server.