Fix Insecure Webhooks in Nuxt
Webhooks are remote triggers that bypass standard authentication flows. In Nuxt, exposing a public server route without HMAC signature verification is a critical vulnerability. Without verification, an attacker can spoof requests, bypass payment gateways, or trigger unauthorized internal logic by simply POSTing a JSON payload to your endpoint. To secure this, you must implement cryptographic signature validation.
The Vulnerable Pattern
export default defineEventHandler(async (event) => { // VULNERABLE: Directly trusting the parsed body without source verification const body = await readBody(event);// Logic executes for any attacker who knows this URL await updateSubscriptionStatus(body.user_id, body.status);
return { success: true }; });
The Secure Implementation
The secure implementation relies on three security primitives. First, we use `readRawBody` instead of `readBody`. Parsed JSON can differ slightly from the original payload (spacing, encoding), which breaks the hash. Second, we use a shared secret known only to the provider (e.g., Stripe, GitHub) and our server to generate a local HMAC. Third, we use `timingSafeEqual`. Standard string comparisons (`===`) return early when a mismatch is found, allowing attackers to brute-force the signature character-by-character by measuring response times. This setup ensures that only the legitimate provider can trigger the event.
import { createHmac, timingSafeEqual } from 'node:crypto';export default defineEventHandler(async (event) => { const config = useRuntimeConfig(); const signature = getHeader(event, ‘x-hub-signature-256’); const secret = config.webhookSecret;
// 1. Get raw body to ensure hash integrity const rawBody = await readRawBody(event, false);
if (!signature || !rawBody) { throw createError({ statusCode: 401, statusMessage: ‘Missing credentials’ }); }
// 2. Re-calculate HMAC-SHA256 const hmac = createHmac(‘sha256’, secret); const digest = Buffer.from(‘sha256=’ + hmac.update(rawBody).digest(‘hex’), ‘utf8’); const checksum = Buffer.from(signature, ‘utf8’);
// 3. Constant-time comparison to prevent timing attacks if (checksum.length !== digest.length || !timingSafeEqual(digest, checksum)) { throw createError({ statusCode: 401, statusMessage: ‘Invalid signature’ }); }
const body = JSON.parse(rawBody.toString()); await updateSubscriptionStatus(body.user_id, body.status);
return { status: ‘verified’ }; });
Your Nuxt API
might be exposed to Insecure Webhooks
74% of Nuxt apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.