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:
- Kunci API (dalam query:
key=sk_quick_123abc) — mengidentifikasi akun pengguna (untuk penghitungan kuota render) - 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_secretsendiri
Contoh Implementasi
Untuk tutorial lengkap dengan berbagai kerangka kerja dan pola, lihat:
- Tutorial JavaScript/Node.js - Next.js, Express, Node.js murni
- Next.js App Router
- Next.js Pages Router
- React
- SvelteKit
- Vue/Nuxt
- Astro
- Express
- Node.js murni
- PHP/Laravel
- Python
- Ruby/Rails
JavaScript (Node.js / Frontend)
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)
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
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
{{-- 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
- Simpan rahasia dengan aman - Gunakan variabel lingkungan atau pengelola rahasia
- Jangan pernah mengekspos rahasia dalam kode sisi klien - Tandatangani URL hanya di server
- Gunakan HTTPS - Mencegah serangan MITM
- Rotasi rahasia secara berkala - Terutama jika telah disusupi
- Pantau tanda tangan yang gagal - Kegagalan berulang dapat mengindikasikan serangan
- Isolasi per pengguna - Setiap kunci API memiliki rahasia HMAC sendiri
Respons Kesalahan
Tanda Tangan Hilang
{
"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
{
"error": "Invalid signature",
"code": "invalid_signature"
}
Pengujian
Buat URL pengujian:
# 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
- Pastikan Anda menggunakan rahasia HMAC yang benar (periksa kunci API di dasbor)
- Periksa kueri kanonis: setiap parameter kueri kecuali
s(termasukkey), nilai yang dienkode rfc3986, kunci diurutkan secara leksikografis - Pastikan tanda tangan berupa heksadesimal huruf kecil (64 karakter)
Mendapatkan HMAC Secret Anda
- Buka Dasbor → API Keys
- Buat API key baru (atau gunakan yang sudah ada)
- Salin HMAC Secret yang ditampilkan di samping API key (hanya ditampilkan sekali!)
- Simpan dengan aman di variabel lingkungan Anda
# Add to .env
OG_RENDER_HMAC_SECRET=<your-64-char-hex-secret>
Pengujian dengan curl
# 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).