Sujet

Guide des signatures HMAC

Ce guide des signatures HMAC vous explique comment construire des signatures HMAC-SHA256 pour FastOG, afin de générer dynamiquement des images Open Graph à la volée — sur abonnement, avec un compte gratuit et un plan Dynamic API (les rendus payants sont propres). Vous pouvez créer la signature manuellement dans n'importe quel langage ici, ou utiliser le Testeur d'images OG gratuit pour voir d'abord le format d'URL en action.

Vue d'ensemble

Toutes les requêtes vers /api/v1/og doivent inclure une signature HMAC-SHA256 pour prouver qu'elles proviennent du site web légitime. Cela empêche des tiers non autorisés d'utiliser votre quota API.

Deux niveaux d'authentification :

  1. Clé API (dans la requête : key=sk_quick_123abc) — identifie le compte utilisateur (pour la comptabilisation du quota de rendu)
  2. Signature HMAC (dans la requête : s=<signature>) — prouve que la requête provient de votre site web

Les deux doivent être valides. La clé API seule ne suffit PAS.

Pourquoi des paramètres de requête ? Les images OG sont récupérées par les robots des réseaux sociaux (LinkedIn, Twitter, Facebook) qui n'envoient pas d'en-têtes personnalisés. La signature DOIT être dans l'URL.

Génération de la signature

Algorithme

method = "GET"
path = "/api/v1/og"
canonicalQuery = for every query param EXCEPT `s` (including `key`):
  - array values are sorted too
  - each pair: rfc3986Encode(key)=rfc3986Encode(value)
  - pairs sorted lexicographically and joined with "&"

message = method + path + "\n" + canonicalQuery
signature = hex(HMAC-SHA256(message, hmac_secret))

Query parameters:
  key=<apiKey>
  s=<signature>

Exigences

  • Format de la signature : chaîne hexadécimale de 64 caractères (SHA256)
  • Utilisez hash_equals() pour une comparaison en temps constant (prévient les attaques temporelles)
  • Secrets par utilisateur : chaque clé API possède son propre champ hmac_secret

Exemples d’implémentations

Pour des tutoriels complets couvrant plusieurs frameworks et modèles, consultez :

JavaScript (Node.js / Frontend)

javascript
const crypto = require("crypto");

// rfc3986-encode: everything except A-Za-z0-9-._~
const enc = (s) =>
    encodeURIComponent(s).replace(
        /[!'()*]/g,
        (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
    );

function generateOgImageUrl(baseUrl, params, apiKey, hmacSecret) {
    // every query param except `s`; key included; arrays expand to one pair each
    const all = { ...params, key: apiKey };
    const pairs = [];
    for (const [k, v] of Object.entries(all)) {
        for (const vv of Array.isArray(v) ? [...v].sort() : [v]) {
            pairs.push(`${enc(k)}=${enc(vv)}`);
        }
    }
    pairs.sort();
    const canonical = pairs.join("&");

    const signature = crypto
        .createHmac("sha256", hmacSecret)
        .update(`GET/api/v1/og\n${canonical}`)
        .digest("hex");

    const url = new URL(baseUrl);
    for (const [k, v] of Object.entries(all)) {
        const isArray = Array.isArray(v);
        for (const vv of isArray ? v : [v]) {
            url.searchParams.append(isArray ? `${k}[]` : k, vv);
        }
    }
    url.searchParams.set("s", signature);
    return url.toString();
}

// Usage in meta tag:
const ogUrl = generateOgImageUrl(
    "https://fastog.com/api/v1/og",
    { title: "Como criar um SaaS", template: "default" },
    "sk_quick_123abc",
    process.env.HMAC_SECRET,
);

// <meta property="og:image" content="{ogUrl}" />

PHP (Laravel)

php
function generateOgImageUrl(string $baseUrl, array $params, string $apiKey, string $hmacSecret): string
{
    // every query param except `s`; key included; array values sorted too
    $all = array_merge($params, ['key' => $apiKey]);

    $pairs = [];
    foreach ($all as $k => $v) {
        $values = is_array($v) ? $v : [$v];
        sort($values, SORT_STRING);
        foreach ($values as $vv) {
            $pairs[] = rawurlencode((string)$k) . '=' . rawurlencode((string)$vv);
        }
    }
    sort($pairs, SORT_STRING);
    $canonical = implode('&', $pairs);

    $signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);

    // URL uses bracket keys for arrays (features[]=a&features[]=b); canonical signs plain keys
    $urlPairs = [];
    foreach ($all as $k => $v) {
        $isArray = is_array($v);
        $values = $isArray ? $v : [$v];
        $urlKey = $isArray ? $k . '[]' : $k;
        foreach ($values as $vv) {
            $urlPairs[] = rawurlencode($urlKey) . '=' . rawurlencode((string)$vv);
        }
    }

    return $baseUrl . '?' . implode('&', $urlPairs) . '&s=' . $signature;
}

// Usage in Blade template:
$ogUrl = generateOgImageUrl(
    'https://fastog.com/api/v1/og',
    ['title' => 'Como criar um SaaS'],
    'sk_quick_123abc',
    env('HMAC_SECRET')
);

// <meta property="og:image" content="{{ $ogUrl }}" />

Python

python
import hmac
import hashlib
from urllib.parse import quote

def _enc(s):
    return quote(str(s), safe='-._~')

def generate_og_image_url(base_url, params, api_key, hmac_secret):
    # every query param except `s`; key included; array values sorted too
    all_params = {**params, 'key': api_key}

    pairs = []
    for k, v in all_params.items():
        for vv in sorted(v if isinstance(v, list) else [v]):
            pairs.append(f"{_enc(k)}={_enc(vv)}")
    canonical = '&'.join(sorted(pairs))

    signature = hmac.new(
        hmac_secret.encode(),
        f"GET/api/v1/og\n{canonical}".encode(),
        hashlib.sha256
    ).hexdigest()

    # URL uses bracket keys for arrays (features[]=a&features[]=b); canonical signs plain keys
    url_pairs = []
    for k, v in all_params.items():
        if isinstance(v, list):
            for vv in v:
                url_pairs.append(f"{_enc(f'{k}[]')}={_enc(vv)}")
        else:
            url_pairs.append(f"{_enc(k)}={_enc(v)}")

    return f"{base_url}?{'&'.join(url_pairs)}&s={signature}"

# Usage:
og_url = generate_og_image_url(
    'https://fastog.com/api/v1/og',
    {'title': 'Como criar um SaaS', 'template': 'default'},
    'sk_quick_123abc',
    os.environ['HMAC_SECRET']
)

# <meta property="og:image" content="{{ og_url }}" />

Exemple complet : modèle Blade Laravel

blade
{{-- In your blog post view --}}
@php
    $apiKey = 'sk_quick_123abc'; // Your full API key
    $hmacSecret = env('OG_RENDER_HMAC_SECRET');

    $params = [
        'template' => 'blog',
        'title' => $post->title,
        'subtitle' => $post->excerpt,
        'key' => $apiKey,
    ];

    // canonical query: sorted "k=v" pairs, rfc3986-encoded, every param except `s`
    $pairs = [];
    foreach ($params as $k => $v) {
        $pairs[] = rawurlencode((string)$k) . '=' . rawurlencode((string)$v);
    }
    sort($pairs, SORT_STRING);
    $canonical = implode('&', $pairs);
    $signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);

    $ogImageUrl = 'https://fastog.com/api/v1/og?' . $canonical . '&s=' . $signature;
@endphp

<head>
    <meta property="og:image" content="{{ $ogImageUrl }}" />
    <meta property="og:title" content="{{ $post->title }}" />
    <meta property="og:description" content="{{ $post->excerpt }}" />
</head>

Bonnes pratiques de sécurité

  1. Stockez les secrets de manière sécurisée - Utilisez des variables d’environnement ou un gestionnaire de secrets
  2. N’exposez jamais les secrets dans le code côté client - Signez les URL uniquement côté serveur
  3. Utilisez HTTPS - Prévient les attaques de type MITM
  4. Faites pivoter les secrets périodiquement - Surtout en cas de compromission
  5. Surveillez les signatures échouées - Des échecs répétés peuvent indiquer des attaques
  6. Isolation par utilisateur - Chaque clé API possède son propre secret HMAC

Réponses d’erreur

Signature manquante

json
{
    "error": "Missing signature parameters",
    "code": "missing_signature",
    "required_params": ["s", "key"],
    "example": "/api/v1/og?title=Hello&key=sk_quick_123abc&s=<signature>"
}

Signature invalide

json
{
    "error": "Invalid signature",
    "code": "invalid_signature"
}

Test

Générez une URL de test :

bash
# Node.js
node -e "
const crypto = require('crypto');
const enc = (s) => encodeURIComponent(s).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
const all = { title: 'Test', key: 'sk_quick_123abc' };
const pairs = [];
for (const [k, v] of Object.entries(all)) for (const vv of Array.isArray(v) ? v : [v]) pairs.push(enc(k) + '=' + enc(vv));
pairs.sort();
const canonical = pairs.join('&');
const sig = crypto.createHmac('sha256', 'YOUR_HMAC_SECRET').update('GET/api/v1/og\n' + canonical).digest('hex');
console.log('https://fastog.com/api/v1/og?title=Test&key=sk_quick_123abc&s=' + sig);
"

# Test with curl
curl "https://fastog.com/api/v1/og?title=Test&key=sk_quick_123abc&s=<signature>"

Réponses attendues :

  • ✅ Signature valide → Renvoie l’image OG (ou une erreur de validation si les paramètres du modèle sont manquants)
  • ❌ Signature invalide → {"error": "...", "code": "invalid_signature"}
  • ❌ Paramètres manquants → {"error": "...", "code": "missing_signature"}

Dépannage

La signature ne correspond pas

  1. Vérifiez que vous utilisez le bon secret HMAC (consultez la clé API dans le tableau de bord)
  2. Vérifiez la requête canonique : chaque paramètre de requête sauf s (y compris key), valeurs encodées en rfc3986, clés triées par ordre lexicographique
  3. Vérifiez que la signature est en hexadécimal minuscule (64 caractères)

Obtention de votre secret HMAC

  1. Allez dans Tableau de bord → Clés API
  2. Créez une nouvelle clé API (ou utilisez une clé existante)
  3. Copiez le secret HMAC affiché à côté de la clé API (affiché une seule fois !)
  4. Stockez-le de manière sécurisée dans vos variables d’environnement
bash
# Add to .env
OG_RENDER_HMAC_SECRET=<your-64-char-hex-secret>

Test avec curl

bash
# Bash script to generate signed URL
SECRET="your-hmac-secret-here"
API_KEY="sk_quick_123abc"  # Your full API key

# canonical query: sorted "k=v" pairs, every param except `s` (key included)
CANONICAL="key=${API_KEY}&template=default&title=Test"
MESSAGE=$(printf 'GET/api/v1/og\n%s' "$CANONICAL")
SIGNATURE=$(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

curl "http://localhost:8000/api/v1/og?template=default&title=Test&key=${API_KEY}&s=${SIGNATURE}"

Résultat attendu : 200 OK avec les données d’image (ou 422 si les paramètres du modèle sont invalides).

Référence complète de l’API

Essayez gratuitement — aucune inscription requise

Prévisualisez et testez des images OG dynamiques en quelques secondes.

Ouvrir le testeur d'images OG gratuit

A BeCaSimulations site