FastOG
Topik

Panduan Tanda Tangan HMAC

Panduan tanda tangan HMAC ini memandu Anda dalam membangun tanda tangan HMAC-SHA256 untuk FastOG, sehingga Anda dapat menghasilkan gambar Open Graph dinamis secara langsung — berbasis langganan, dengan akun gratis dan paket Dynamic API (render berbayar bersih). Buat tanda tangan secara manual dalam bahasa apa pun di sini, atau gunakan Penguji Gambar OG Gratis untuk melihat format URL secara langsung terlebih dahulu.

Ringkasan

Semua permintaan ke /api/v1/og harus menyertakan tanda tangan HMAC-SHA256 untuk membuktikan bahwa permintaan tersebut berasal dari situs web yang sah. Hal ini mencegah pihak yang tidak berwenang menggunakan kuota API Anda.

Dua lapis autentikasi:

  1. Kunci API (dalam query: key=sk_quick_123abc) — mengidentifikasi akun pengguna (untuk penghitungan kuota render)
  2. Tanda Tangan HMAC (dalam query: s=<signature>) — membuktikan bahwa permintaan berasal dari situs web Anda

Keduanya harus lolos. Kunci API saja TIDAK cukup.

Mengapa parameter query? Gambar OG diambil oleh perayap jaringan sosial (LinkedIn, Twitter, Facebook) yang tidak mengirimkan header kustom. Tanda tangan HARUS berada di dalam URL.

Membuat Tanda Tangan

Algoritma

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>

Persyaratan

  • Format tanda tangan: string hex 64 karakter (SHA256)
  • Gunakan hash_equals() untuk perbandingan waktu konstan (mencegah serangan timing)
  • Rahasia per pengguna: Setiap kunci API memiliki kolom hmac_secret sendiri

Contoh Implementasi

Untuk tutorial lengkap dengan berbagai kerangka kerja dan pola, lihat:

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 }}" />

Contoh Lengkap: Template Laravel Blade

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>

Praktik Terbaik Keamanan

  1. Simpan rahasia dengan aman - Gunakan variabel lingkungan atau pengelola rahasia
  2. Jangan pernah mengekspos rahasia dalam kode sisi klien - Tandatangani URL hanya di server
  3. Gunakan HTTPS - Mencegah serangan MITM
  4. Rotasi rahasia secara berkala - Terutama jika telah disusupi
  5. Pantau tanda tangan yang gagal - Kegagalan berulang dapat mengindikasikan serangan
  6. Isolasi per pengguna - Setiap kunci API memiliki rahasia HMAC sendiri

Respons Kesalahan

Tanda Tangan Hilang

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

Tanda Tangan Tidak Valid

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

Pengujian

Buat URL pengujian:

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>"

Respons yang diharapkan:

  • ✅ Tanda tangan valid → Mengembalikan gambar OG (atau error validasi jika parameter template tidak ada)
  • ❌ Tanda tangan tidak valid → {"error": "...", "code": "invalid_signature"}
  • ❌ Parameter tidak ada → {"error": "...", "code": "missing_signature"}

Pemecahan Masalah

Tanda tangan tidak cocok

  1. Pastikan Anda menggunakan rahasia HMAC yang benar (periksa kunci API di dasbor)
  2. Periksa kueri kanonis: setiap parameter kueri kecuali s (termasuk key), nilai yang dienkode rfc3986, kunci diurutkan secara leksikografis
  3. Pastikan tanda tangan berupa heksadesimal huruf kecil (64 karakter)

Mendapatkan HMAC Secret Anda

  1. Buka Dasbor → API Keys
  2. Buat API key baru (atau gunakan yang sudah ada)
  3. Salin HMAC Secret yang ditampilkan di samping API key (hanya ditampilkan sekali!)
  4. Simpan dengan aman di variabel lingkungan Anda
bash
# Add to .env
OG_RENDER_HMAC_SECRET=<your-64-char-hex-secret>

Pengujian dengan 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}"

Hasil yang diharapkan: 200 OK dengan data gambar (atau 422 jika parameter template tidak valid).

Referensi API Lengkap

Coba gratis — tanpa perlu mendaftar

Pratinjau dan uji gambar OG dinamis dalam hitungan detik.

Buka Penguji Gambar OG Gratis

A BeCaSimulations site