Validação HMAC
A validação HMAC é a forma como a FastOG verifica que cada pedido de imagem OG baseado em subscrição veio do seu próprio servidor — URLs assinados mantêm os seus créditos seguros. Registe-se, escolha um plano Dynamic API e crie a sua chave de API e segredo HMAC no painel de controlo (dashboard); os renders de API pagos são limpos. Esta página cobre as regras do lado do servidor por trás das imagens Open Graph dinâmicas, desde a assinatura canónica de consultas até à comparação em tempo constante.
Visão geral
A validação de assinatura HMAC-SHA256 foi adicionada ao endpoint /api/v1/og para impedir a utilização não autorizada da quota da API. Isto garante que apenas os pedidos provenientes do website legítimo podem gerar imagens OG.
Arquitetura
Autenticação em duas camadas:
- Assinatura HMAC (primeira) — Valida que o pedido foi originado de uma fonte confiável (o website)
- Chave de API (segunda) — Identifica a conta do utilizador para efeitos de contabilização da quota de renderização
Middleware único (ApiKeyAuth): verifica a assinatura (parâmetros de consulta) e resolve a chave de API para a contabilização da quota de renderização numa única passagem — falha rapidamente em assinaturas inválidas.
Parâmetros de consulta, não cabeçalhos: Como as imagens OG são obtidas pelos crawlers das redes sociais (LinkedIn, Twitter, Facebook) que não enviam cabeçalhos personalizados, a assinatura é passada nos parâmetros de consulta do URL:
GET /api/v1/og?title=Hello&key=sk_quick_123abc&s=<hmac>
Implementação
Ficheiros Criados/Modificados
app/Http/Middleware/HmacSignature.php- Novo middleware que valida assinaturas HMAC (parâmetros de consulta)routes/api.php- Adicionado middleware HMAC à rota OGbootstrap/app.php- Registado o alias do middlewarehmacapp/Models/ApiKey.php- Adicionado o campohmac_secret(segredos por utilizador)app/Http/Controllers/DashboardController.php- Gera segredo HMAC na criação da chave de APIdatabase/migrations/2026_08_03_214931_add_hmac_secret_to_api_keys_table.php- Migração para a coluna hmac_secretresources/js/Pages/Dashboard/ApiKeys.svelte- Mostra o segredo HMAC ao utilizador (uma única vez)docs/hmac-signature-guide.md- Guia de implementação para o clientetests/Feature/HmacSignatureTest.php- 8 testes a passar
Algoritmo de Assinatura
method = HTTP method (e.g., "GET")
path = request path (e.g., "/api/v1/og")
query = the request's DECODED query params
canonical = every param except `s`, keys sorted (array values sorted too),
each `key=value` RFC-3986 encoded (unreserved-only), joined with "&"
message = method + "/" + path + "\n" + canonical
signature = HMAC-SHA256(message, hmac_secret)
Query parameters:
key=<apiKey>
s=<signature>
(no timestamp)
key faz parte da consulta canónica, pelo que a assinatura está vinculada à chave de API. Alterar qualquer parâmetro (título, modelo, imagem, …) ou a própria chave altera a consulta canónica → incompatibilidade de assinatura → 401 → sem renderização, sem contagem de quota. Parâmetros idênticos → assinatura idêntica → URL idêntico → armazenável em cache no edge para sempre, reproduzido sem contagem.
Funcionalidades de Segurança
- Assinatura com endereçamento por conteúdo - Qualquer alteração de parâmetro → 401 → sem renderização, sem contagem de quota (o próprio URL é a prova)
- Sem timestamp - Os URLs são incorporados em HTML público e armazenados em cache para sempre; têm de sobreviver a 5 minutos
- Comparação em tempo constante (
hash_equals()) - Evita ataques de temporização - Chave de API na mensagem - A assinatura está vinculada à chave; trocar de chave falha
- Específica do caminho - A assinatura só é válida para o caminho exato
- Específica do método - Uma assinatura GET não funciona para POST
- Segredos por utilizador - Cada chave de API tem o seu próprio segredo HMAC, podendo ser revogado de forma independente (rotação = interruptor de emergência para URLs divulgados)
Configuração
Gerar Segredo (Automático)
O segredo HMAC é gerado automaticamente ao criar uma chave de API no painel de controlo (dashboard). É apresentado uma única vez juntamente com a chave de API e nunca mais é mostrado novamente.
Geração manual (se necessário):
openssl rand -hex 32
Esquema da Base de Dados
ALTER TABLE api_keys ADD COLUMN hmac_secret VARCHAR(64)
COMMENT 'HMAC-SHA256 secret for signing OG image URLs (hex, 64 chars)';
Variáveis de Ambiente
Não é necessária nenhuma variável de ambiente global — os segredos são armazenados por utilizador na tabela api_keys.
Exemplo de Utilização (Backend do Website)
// Node.js example - generate signed OG image URL
const crypto = require("crypto");
// RFC-3986 encode (encodeURIComponent leaves !'()* unencoded).
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
function generateOgImageUrl(baseUrl, params, apiKey, hmacSecret) {
const all = { ...params, key: apiKey };
// Canonical: every param (except s), sorted, RFC-3986 encoded, joined with "&".
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 message = `GET/api/v1/og\n${canonical}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.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: "blog" },
"sk_quick_123abc",
process.env.HMAC_SECRET,
);
// <meta property="og:image" content="{ogUrl}" />
Respostas de Erro
| Estado | Código | Descrição |
|---|---|---|
| 401 | missing_signature |
Parâmetro de consulta key ou s em falta |
| 401 | invalid_signature |
A assinatura não corresponde à consulta canónica (alterada ou segredo errado) |
Testes
Execute os testes:
php artisan test --filter HmacSignatureTest
Todos os 8 testes passam:
- ✅ Pedido sem assinatura é rejeitado
- ✅ Pedido com parâmetro de chave em falta é rejeitado
- ✅ Parâmetro de conteúdo adulterado (título alterado) é rejeitado
- ✅ Parâmetro adicionado é rejeitado
- ✅ Assinatura inválida é rejeitada
- ✅ Assinatura válida passa na verificação HMAC
- ✅ A assinatura é específica do caminho
- ✅ A assinatura é específica do método
- ✅ Chaves de API diferentes têm segredos diferentes
Impacto no Desempenho
- Desprezível - O cálculo de HMAC-SHA256 é de ~0,1ms por pedido
- Sem chamadas de rede adicionais - A validação é local
- Falha rápida - Assinaturas inválidas são rejeitadas antes da pesquisa da chave de API
Considerações de Segurança
- Nunca exponha o segredo em código do lado do cliente - Assine apenas no servidor
- Use HTTPS - O HMAC protege contra pedidos não autorizados, o HTTPS protege contra ataques MITM
- Rode os segredos periodicamente - Especialmente se forem comprometidos (requer regenerar a chave da API)
- Monitore assinaturas falhadas - Falhas repetidas podem indicar ataques
- Isolamento por utilizador - Cada chave da API tem o seu próprio segredo HMAC; o comprometimento de uma não afeta as outras
Referências
- Guia de Assinatura HMAC para Clientes
- Guia de Início Rápido
- OWASP: Folha de Referência para Armazenamento Criptográfico
- PHP: hash_hmac()