주제

PHP & Laravel에서의 OG 이미지

이 가이드는 FastOG를 사용한 PHP 및 Laravel 환경에서 OG 이미지를 처리하는 방법을 다룹니다. 백엔드에서 HMAC URL에 서명하고, 이를 메타 태그에 삽입하면 블로그 게시물이 자동으로 동적인 Open Graph 이미지를 갖게 됩니다. 구독 기반 서비스이며 무료 계정으로 시작할 수 있습니다(Dynamic API 플랜 선택; 대시보드에서 API 키 생성). 연결하기 전에 Free OG Image Tester를 사용하여 템플릿을 미리 확인할 수 있습니다.

PHP 및 Laravel에서 HMAC 서명된 OG 이미지 URL을 구현하는 완전한 가이드입니다.

빠른 시작

1. 서명자 서비스 생성하기

php
// app/Services/QuickogSigner.php
namespace App\Services;

class QuickogSigner
{
    public function generateSignedOgUrl(array $params, array $options = []): string
    {
        $baseUrl = $options['base_url'] ?? config('services.fastog.base_url', 'https://fastog.com/api/v1/og');
        $apiKey = $options['api_key'] ?? config('services.fastog.api_key');
        $hmacSecret = $options['hmac_secret'] ?? config('services.fastog.hmac_secret');

        if (!$apiKey || !$hmacSecret) {
            throw new \RuntimeException('Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET');
        }

        // Canonical query: every param except `s`, including `key`
        $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);

        return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]), '', '&', PHP_QUERY_RFC3986);
    }
}

왜 rawurlencode인가? 표준 쿼리는 RFC-3986에 따라 퍼센트 인코딩됩니다(공백 → %20, + 아님). rawurlencode는 이를 정확히 따르지만, urlencode은 +을 생성하므로 서명이 깨지고 invalid_signature이 반환됩니다. PHP_QUERY_RFC3986를 http_build_query에 전달하면 최종 URL이 동일한 인코딩 상태를 유지합니다.

2. 구성

php
// config/services.php
return [
    // ...
    'fastog' => [
        'api_key' => env('FASTOG_API_KEY'),
        'hmac_secret' => env('FASTOG_HMAC_SECRET'),
        'base_url' => env('FASTOG_BASE_URL', 'https://fastog.com/api/v1/og'),
    ],
];
bash
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here

3. 싱글톤으로 등록하기

php
// app/Providers/AppServiceProvider.php
use App\Services\QuickogSigner;

public function register(): void
{
    $this->app->singleton(QuickogSigner::class);
}

Laravel Blade

blade
{{-- resources/views/blog/show.blade.php --}}
@extends('layouts.app')

@section('head')
    <meta property="og:image" content="{{ $ogImageUrl }}" />
    <meta property="og:title" content="{{ $post->title }}" />
    <meta property="og:description" content="{{ $post->excerpt }}" />
@endsection

@section('content')
    <article>
        <h1>{{ $post->title }}</h1>
        <p>{{ $post->excerpt }}</p>
    </article>
@endsection

컨트롤러

php
// app/Http/Controllers/BlogController.php
namespace App\Http\Controllers;

use App\Services\QuickogSigner;
use App\Models\Post;

class BlogController extends Controller
{
    public function show(Post $post, QuickogSigner $signer)
    {
        $ogImageUrl = $signer->generateSignedOgUrl([
            'template' => 'blog',
            'title' => $post->title,
            'subtitle' => $post->excerpt,
            'author' => $post->author->name,
        ]);

        return view('blog.show', compact('post', 'ogImageUrl'));
    }
}

API 라우트

php
// routes/api.php
use App\Services\QuickogSigner;
use Illuminate\Http\Request;

Route::get('/og', function (Request $request, QuickogSigner $signer) {
    $validated = $request->validate([
        'title' => 'required|string',
        'template' => 'nullable|string',
    ]);

    $ogImageUrl = $signer->generateSignedOgUrl([
        'template' => $validated['template'] ?? 'default',
        'title' => $validated['title'],
    ]);

    return response()->json(['ogImageUrl' => $ogImageUrl]);
});

이커머스 예제

php
// app/Http/Controllers/ProductController.php
public function show(Product $product, QuickogSigner $signer)
{
    $ogImageUrl = $signer->generateSignedOgUrl([
        'template' => 'ecommerce',
        'title' => $product->name,
        'price' => $product->formatted_price,
        'image' => $product->image_url,
    ]);

    return view('products.show', compact('product', 'ogImageUrl'));
}

블로그 게시물 예제

php
// app/Http/Controllers/ProfileController.php
public function show(User $user, QuickogSigner $signer)
{
    $ogImageUrl = $signer->generateSignedOgUrl([
        'template' => 'blog',
        'title' => $user->name,
        'subtitle' => $user->bio,
        'author' => $user->name,
    ]);

    return view('profile.show', compact('user', 'ogImageUrl'));
}

일반 PHP (프레임워크 없음)

php
// lib/fastog-signer.php
function generateSignedOgUrl(array $params, array $options = []): string
{
    $baseUrl = $options['base_url'] ?? 'https://fastog.com/api/v1/og';
    $apiKey = $options['api_key'] ?? $_ENV['FASTOG_API_KEY'];
    $hmacSecret = $options['hmac_secret'] ?? $_ENV['FASTOG_HMAC_SECRET'];

    $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);

    return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]), '', '&', PHP_QUERY_RFC3986);
}
php
// index.php
require_once 'lib/fastog-signer.php';

$ogUrl = generateSignedOgUrl([
    'template' => 'blog',
    'title' => 'My Blog Post',
]);
?>
<!DOCTYPE html>
<html>
<head>
    <meta property="og:image" content="<?= htmlspecialchars($ogUrl) ?>" />
    <meta property="og:title" content="My Blog Post" />
</head>
<body>
    <h1>My Blog Post</h1>
</body>
</html>

일반적인 함정

잘못된 메시지 서명

❌ 잘못됨: hash_hmac('sha256', 'GET/api/v1/og' . $canonical, $hmacSecret) — 개행 문자 구분자가 누락됨. ✅ 올바름: hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret) — 메서드 + 경로, 개행 문자, 그 다음 캐노니컬 쿼리.

key 서명에서 누락

❌ 잘못됨: 콘텐츠 파라미터만 서명하고 key을 URL 뒤에 덧붙이는 경우. ✅ 올바름: key는 캐노니컬 쿼리를 빌드하기 전에 파라미터에 병합되며, 최종 URL에 나타납니다.

중복 파라미터 (배열)

배열 값을 generateSignedOgUrl에 전달하고 http_build_query가 URL을 작성하도록 하세요. 이는 대괄호 키(features[0]=a&features[1]=b)를 생성하는 반면, 캐노니컬 서명은 평범한 중복 키(features=a&features=b)를 사용합니다. URL에서 features=a&features=b를 직접 빌드하지 마세요 — PHP는 중복 키를 스칼라 값으로 디코딩하므로(마지막 값이 승리함), 서버가 다른 캐노니컬을 다시 빌드하여 invalid_signature로 거부합니다.

401 missing_signature / invalid_signature

missing_signature (401)은 URL에서 key 또는 s가 누락되었음을 의미합니다. invalid_signature (401)은 캐노니컬이 일치하지 않았음을 의미합니다 — 서명된 메시지의 개행 문자, 양쪽의 동일한 hmac_secret, 모든 키와 값에 대한 rawurlencode(urlencode 아님), 그리고 URL의 중복 파라미터에 대한 대괄호 키를 확인하세요.

HMAC 시크릿 노출

프론트엔드 JavaScript나 공개 설정 파일에서 FASTOG_HMAC_SECRET를 노출하지 마세요.

전체 API 참조

무료로 체험해 보세요 — 가입 불필요

몇 초 만에 동적 OG 이미지를 미리 보고 테스트하세요.

무료 OG 이미지 테스터 열기

A BeCaSimulations site