الموضوع

دليل توقيع HMAC

يأخذك دليل توقيع HMAC هذا خطوة بخطوة عبر بناء توقيعات HMAC-SHA256 لـ FastOG، بحيث يمكنك إنشاء صور Open Graph ديناميكية عند الطلب — قائمة على الاشتراك، مع حساب مجاني وخطة Dynamic API (العروض المدفوعة نظيفة). يمكنك كتابة التوقيع يدويًا بأي لغة هنا، أو استخدام Free OG Image Tester لرؤية تنسيق URL عمليًا أولاً.

نظرة عامة

يجب أن تتضمن جميع الطلبات الموجهة إلى /api/v1/og توقيع HMAC-SHA256 لإثبات أنها صادرة من الموقع الإلكتروني الشرعي. يمنع هذا الأطراف غير المصرح لها من استهلاك حصتك من واجهة برمجة التطبيقات (API).

طبقتان من المصادقة:

  1. مفتاح واجهة برمجة التطبيقات (API Key) (في الاستعلام: key=sk_quick_123abc) — يحدد حساب المستخدم (لأغراض احتساب حصة العرض)
  2. توقيع HMAC (في الاستعلام: s=<signature>) — يثبت أن الطلب صادر من موقعك الإلكتروني

يجب أن ينجح كلاهما. مفتاح واجهة برمجة التطبيقات وحده غير كافٍ.

لماذا معاملات الاستعلام؟ يتم جلب صور OG بواسطة زاحفات شبكات التواصل الاجتماعي (LinkedIn، Twitter، Facebook) التي لا ترسل ترويسات مخصصة. يجب أن يكون التوقيع في عنوان URL.

توليد التوقيع

الخوارزمية

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>

المتطلبات

  • صيغة التوقيع: سلسلة hex مكوّنة من 64 حرفًا (SHA256)
  • استخدم hash_equals() للمقارنة في وقت ثابت (يمنع هجمات التوقيت)
  • أسرار لكل مستخدم: كل مفتاح API يحتوي على حقل hmac_secret خاص به

أمثلة على التطبيقات

للدروس الكاملة التي تغطي أطر عمل وأنماط متعددة، راجع:

JavaScript (Node.js / الواجهة الأمامية)

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

مثال كامل: قالب 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>

أفضل ممارسات الأمان

  1. تخزين الأسرار بشكل آمن - استخدم متغيرات البيئة أو مدير أسرار
  2. لا تعرض الأسرار أبدًا في كود جانب العميل - وقّع على الروابط على الخادم فقط
  3. استخدم HTTPS - يمنع هجمات الوسيط (MITM)
  4. دوّر الأسرار بشكل دوري - خاصةً إذا تم اختراقها
  5. راقب التوقيعات الفاشلة - قد تشير حالات الفشل المتكررة إلى هجمات
  6. عزل لكل مستخدم - لكل مفتاح API سر HMAC خاص به

استجابات الأخطاء

توقيع مفقود

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

توقيع غير صالح

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

الاختبار

أنشئ رابط اختبار:

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

الاستجابات المتوقعة:

  • ✅ توقيع صالح ← يُرجع صورة OG (أو خطأ تحقق إذا كانت معاملات القالب مفقودة)
  • ❌ توقيع غير صالح ← {"error": "...", "code": "invalid_signature"}
  • ❌ معاملات مفقودة ← {"error": "...", "code": "missing_signature"}

استكشاف الأخطاء وإصلاحها

التوقيع غير متطابق

  1. تحقق من استخدامك لسر HMAC الصحيح (تحقق من مفتاح API في لوحة التحكم)
  2. تحقق من الاستعلام الأساسي: كل معاملات الاستعلام باستثناء s (بما في ذلك key)، القيم مشفرة وفقًا لـ rfc3986، المفاتيح مرتبة معجميًا
  3. تحقق من أن التوقيع بصيغة سداسية عشرية صغيرة (64 حرفًا)

الحصول على سر HMAC الخاص بك

  1. انتقل إلى لوحة التحكم ← مفاتيح API
  2. أنشئ مفتاح API جديدًا (أو استخدم مفتاحًا موجودًا)
  3. انسخ سر HMAC المعروض بجانب مفتاح API (يُعرض مرة واحدة فقط!)
  4. خزّنه بأمان في متغيرات البيئة الخاصة بك
bash
# Add to .env
OG_RENDER_HMAC_SECRET=<your-64-char-hex-secret>

الاختبار باستخدام 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}"

المتوقع: 200 OK مع بيانات الصورة (أو 422 إذا كانت معاملات القالب غير صالحة).

مرجع API الكامل

جرّبه مجانًا — لا حاجة للتسجيل

معاينة واختبار صور OG الديناميكية في ثوانٍ.

افتح أداة اختبار OG المجانية

A BeCaSimulations site