@prokodo/n8n-nodes-secure-webhook
v0.0.1
Published
Hardened webhook trigger for n8n with HMAC/JWT (JWKS), replay protection, IP policies, mTLS proxy checks, per-IP rate limiting, Redis HA state, and audit export.
Downloads
19
Maintainers
Readme
Production-grade webhook trigger for n8n with HMAC/JWT auth, replay protection, IP policies, mTLS header checks, per-IP rate limiting, Redis HA support, and external audit export — developed by prokodo.
🇺🇸 Need help implementing secure n8n webhooks & workflows (HMAC, JWT/JWKS, mTLS, Redis, rate limits)?
prokodo — n8n Security & Automation → click here🇩🇪 Sie brauchen Unterstützung bei sicheren n8n Webhooks & Workflows (HMAC, JWT/JWKS, mTLS, Redis, Rate Limits)?
prokodo — n8n Security & Automation → hier klicken
✨ Features
- 🔐 Authentication profiles
- HMAC: raw-body or extended signature (method + path + query + timestamp + nonce + bodyhash)
- JWT (JWKS): verify
Authorization: Bearer <jwt>against your IdP - API Key: lightweight static header (best for internal calls)
- Combo: HMAC + IP allow/deny (CIDR)
- 🛡️ Replay protection (timestamp + nonce, one-time use)
- 🧱 Rate limiting per IP (in-memory token bucket or Redis Lua fixed-window)
- 🌐 IP policies (allow/deny by CIDR) for partner/VPN hardening
- 📜 mTLS proof via proxy headers (e.g.,
x-ssl-client-verify: SUCCESS) - 🧾 Audit export (best-effort HTTPS POST of accept/reject metadata)
- 🧰 HA/Cluster-ready with optional Redis for rate+replay state
- ⚙️ Strict input hygiene: Content-Type allowlist & body size cap
- 🔁 Dual-key rotation window for HMAC secret rollovers
- 🧩 UX-friendly node options with comprehensive inline help
Internal node name: prokodoSecureWebhook
In the node picker: “Secure Webhook”
🆚 Why not the default n8n Webhook?
| Capability | n8n Default Webhook | Secure Webhook (this node) | |---|---|---| | Auth | Token in URL or basic header patterns | HMAC / JWT (JWKS) / API Key / Combo | | Replay protection | ❌ | Timestamp + Nonce | | Rate limiting | ❌ | Per-IP (memory or Redis) | | IP policies | ❌ | Allow/Deny (CIDR) | | mTLS awareness | ❌ | Proxy header checks | | Audit trail | Minimal | External HTTPS audit events | | HA state | N/A | Redis for nonce & rate data | | Signature scope | N/A | Raw body or Extended (method+path+query+…) |
Bottom line: If your webhook is public-facing or exposed to the internet, this hardened trigger offers defense-in-depth that the default webhook does not.
✅ Requirements
- Node.js 18+ / 22 LTS
- n8n ≥ 1.88 (tested on 1.116.2+)
- (Optional) Redis for HA/cluster setups
- For HMAC: a shared secret in n8n credentials (
hmacSharedSecretApi) - For JWT: a reachable JWKS URL (
jwtJwksApi) and optionaliss/aud - For API Key: header name + value (
apiKeyHeaderApi) - For mTLS headers: a reverse proxy (Nginx/Envoy/Cloudflare) that terminates TLS and forwards verification headers
Using an older n8n (e.g. 1.88)? It may still work if you align n8n-core / n8n-workflow versions. For best results, upgrade n8n.
📦 Install
Option A — Custom extensions folder (recommended)
Local n8n (not Docker):
# choose your custom folder (default ~/.n8n)
export N8N_CUSTOM_EXTENSIONS=~/.n8n
# install your built package into that folder
npm install --prefix "$N8N_CUSTOM_EXTENSIONS" @prokodo/n8n-nodes-secure-webhook
# start n8n
n8n startDocker (example Dockerfile):
FROM n8nio/n8n:latest
ENV N8N_CUSTOM_EXTENSIONS=/home/node/.n8n
ENV NODE_PATH=/home/node/.n8n/node_modules
USER node
RUN npm install --prefix /home/node/.n8n @prokodo/n8n-nodes-secure-webhook@latestAfter starting n8n, search in the node picker for “Secure Webhook” Internal name: prokodoSecureWebhook
🛠 Dev install (build + link locally)
# in this repo
npm ci
npm run build
# make your package linkable
npm link
# link into your n8n custom extensions folder
npm link @prokodo/n8n-nodes-secure-webhook --prefix ~/.n8n
# start n8n with your custom folder
export N8N_CUSTOM_EXTENSIONS=~/.n8n
n8n startPublish-ready tip: This package publishes compiled JS from dist/ to npm. You don’t need to commit dist/ to Git. To support installs straight from GitHub, add:
"scripts": {
"prepare": "npm run build"
}…and commit src/ (not dist/).
🔧 Webhook Endpoint
Path: /secure, method: POST, response mode: onReceived.
Final URL (default n8n): https://<your-host>/webhook/secure
🔐 Credentials
- HMAC → Create credentials
hmacSharedSecretApiand set your shared secret. - JWT (JWKS) → Create
jwtJwksApiwith your JWKS URL (e.g., Auth0/Okta). Optionally setjwtIss/jwtAudin the node. - API Key → Create
apiKeyHeaderApiwithheaderName(e.g.,x-api-key) andvalue.
Pick the Security Profile in the node to match the credential type you configured.
🧩 Node Options (Field Guide)
Core
Security Profile
- HMAC — safest default for public internet
- jwks (JWT) — for IdPs like Auth0/Okta/OIDC
- apikey (Static header) — simple, for internal clients
- combo — HMAC + IP allow/deny
Shared safety
- Trusted Proxy Hops — how many
X-Forwarded-Forhops you trust (e.g., Cloudflare + Nginx = 2) - Max Body (bytes) — reject large payloads early (default 1 MB)
- Allowed Content-Types — comma-separated allowlist (default:
application/json,application/x-www-form-urlencoded) - Rate Limit / Minute (per IP) — default 120. With Redis it’s cluster-wide; otherwise per process
Replay Protection
- Timestamp Header (
x-timestamp) — UNIX seconds, 10 digits - Nonce Header (
x-nonce) — unique per request (UUID v4 or 16–32B hex) - Max Skew (sec) — default 300; tune based on client/server time sync
HMAC (recommended/combo)
- HMAC Signature Header (
x-signature) → value likesha256=<hex> - HMAC Algorithm —
sha256(default) orsha512
Signature Mode
- body — sign raw body with HMAC (compatibility)
- extended — method + path + canonical query + timestamp + nonce + bodyhash (strongest)
Dual-Key Rotation
- Previous Secret — optional, for rolling changes
- Dual-Key Window (min) — accept previous secret briefly during rotation
IP Policies (combo)
- IP Allow (CIDR) — only allow these ranges
- IP Deny (CIDR) — block these ranges first (deny takes precedence)
JWT (JWKS)
- JWT Issuer (iss) — optional but recommended
- JWT Audience (aud) — optional but recommended
- JWKS Cooldown (ms) — min interval between JWKS fetches (default 60s)
- JWKS Fetch Timeout (ms) — network timeout (default 3s)
mTLS via Proxy
- Require mTLS Header — expect proof from your proxy (e.g.,
x-ssl-client-verify: SUCCESS) - Verify Header — default
x-ssl-client-verify - Subject Header — default
x-ssl-client-dn - Subject Allow Regex — optional (
^CN=partner\.), fail-closed on bad regex
Audit Export
- Audit Export —
noneorhttps - Audit Endpoint URL — HTTPS receiver of compact JSON events
- Audit Bearer Token — optional
Authorization: Bearer <token>
🔐 Client Integration Scenarios
1) HMAC (Recommended)
Server:
- Create credentials
hmacSharedSecretApiwith your shared secret. - Profile: Empfohlen (HMAC).
- Keep default timestamp/nonce headers unless you need custom names.
Client (Node.js, extended mode):
import crypto from 'node:crypto';
const url = 'https://your-n8n.example/secure?foo=bar';
const body = JSON.stringify({ hello: 'world' });
const ts = Math.floor(Date.now()/1000).toString();
const nonce = crypto.randomUUID();
const algo = 'sha256';
// body hash
const bodyHash = crypto.createHash(algo).update(Buffer.from(body)).digest('hex');
// canonicalize query
const { URL } = await import('node:url');
const u = new URL(url);
const query = [...u.searchParams.entries()]
.sort(([a],[b]) => a.localeCompare(b))
.map(([k,v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
// base string
const base = ['POST', u.pathname, query, ts, nonce, bodyHash].join('\n');
// hmac
const signatureHex = crypto.createHmac(algo, process.env.WEBHOOK_SECRET).update(base).digest('hex');
const resp = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-timestamp': ts,
'x-nonce': nonce,
'x-signature': `${algo}=${signatureHex}`,
},
body,
});
console.log(resp.status, await resp.text());curl (body mode):
TS=$(date +%s)
NONCE=$(uuidgen)
BODY='{"hello":"world"}'
SIG_HEX=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | xxd -p -c 256)
curl -X POST "https://your-n8n.example/secure" \
-H "content-type: application/json" \
-H "x-timestamp: $TS" \
-H "x-nonce: $NONCE" \
-H "x-signature: sha256=$SIG_HEX" \
--data "$BODY"2) JWT (JWKS)
Server:
- Create credentials jwtJwksApi with your JWKS URL.
- Profile: JWT (JWKS). Optionally set jwtIss/jwtAud.
Client:
curl -X POST "https://your-n8n.example/secure" \
-H "content-type: application/json" \
-H "authorization: Bearer $JWT" \
--data '{"ok":true}'3) API Key (Internal)
Server:
- Create credentials
apiKeyHeaderApiwith:headerName: e.g.,x-api-keyvalue: your static secret
- Profile: Einfacher API Key.
Client:
curl -X POST "https://your-n8n.example/secure" \
-H "content-type: application/json" \
-H "x-api-key: YOUR_SECRET" \
--data '{"ok":true}'4) Combo (HMAC + IP Allow/Deny)
Server:
- Profile: Kombiniert.
- Configure HMAC and IP Allow/Deny CIDRs.
Client: Same as HMAC, but requests must originate from allowed networks.
🔁 HMAC Secret Rotation (Playbook)
- Set Previous Secret = current secret; deploy the node with a Dual-Key Window (e.g., 60 min).
- Distribute the new secret to clients and switch their signing key.
- After the window elapses and traffic is fully migrated, clear Previous Secret.
🧭 Reverse Proxy (mTLS Header) Example
Nginx:
ssl_verify_client on; # request client cert
ssl_client_certificate /etc/nginx/ca_chain.pem;
location /secure {
proxy_pass http://n8n:5678/secure;
proxy_set_header x-ssl-client-verify $ssl_client_verify; # "SUCCESS" on pass
proxy_set_header x-ssl-client-dn $ssl_client_s_dn;
}Node settings
- Enable Require mTLS Header
- Keep defaults or adjust header names
- Optionally Subject Allow Regex like
^CN=partner\.
🧮 Rate Limiting Details
- In-memory (default): token bucket (burst friendly), per process
- Redis (optional): fixed window via atomic Lua (
EVALSHA), cluster-safe
Fields
- Rate Limit / Minute (per IP): max hits per 60s
- Redis aktivieren / URL / Namespace: shared state across nodes
- Returns 429 with
Retry-Afterwhen exceeded
🗂 Audit Export (Best-Effort)
If enabled, the node POSTs a compact JSON event to your endpoint:
{
"ts": "2025-01-01T10:00:00.000Z",
"rid": "9f2a…",
"ip": "203.0.113.42",
"profile": "recommended",
"outcome": "accept",
"reason": "bad_signature"
}- No request payload is sent (metadata only).
- Failures are swallowed to avoid breaking the main request path.
🔒 Security Model (At a Glance)
- Input hygiene: strict Content-Type allowlist, body size cap
- Auth: HMAC/JWT/API Key; extended HMAC defends against path/query tampering
- Replay: timestamp + nonce (short TTL); requires client time sync
- Abuse: per-IP rate limiting; optional IP allow/deny (CIDR)
- Transport: terminate TLS at edge; optional mTLS forwarding
- State: Redis for HA (nonces & rate); memory fallback for simple installs
- Audit: external events for SIEM/forensics (no payload leakage)
🧪 Testing Checklist
- ✅ Valid Content-Type and within Max Body
- ✅ Include x-timestamp (10-digit seconds) within Max Skew
- ✅ Include x-nonce (unique per request)
- ✅ HMAC: correct signature (body/extended mode)
- ✅ JWT: valid token & reachable JWKS;
iss/audmatch if configured - ✅ Rate limiting: send >
rateMax/min to observe 429 + Retry-After - ✅ IP policy behavior (allow/deny)
- ✅ (Optional) mTLS headers forwarding via proxy
- ✅ Observe x-request-id and audit events
🧯 Troubleshooting
- 415
unsupported_content_type→ Add client’s type to Allowed Content-Types - 413
payload_too_large→ Increase Max Body or shrink payload - 401
bad_or_missing_timestamp→ Ensurex-timestampwithin Max Skew - 401
replay_detected→ Use a freshx-noncefor every request - 401
bad_signature/bad_signature_algo→ Check algorithm/header and base string mode - 401
invalid_token/missing_bearer→ SendAuthorization: Bearer <jwt>; verify IdP/JWKS - 401
mtls_*→ Confirm proxy sets verification headers - 403
ip_denied/ip_not_allowed→ Adjust IP Deny/Allow CIDRs - 429
rate_limited→ Decrease traffic, raise threshold, or enable Redis
🧠 Best Practices
- Prefer HMAC extended mode for public endpoints.
- Keep Max Skew low (120–300s) and ensure NTP on all systems.
- Rotate HMAC secrets regularly; use Dual-Key Window during rollouts.
- For HA/Cluster, enable Redis (protect with TLS/network policy).
- Terminate TLS at the edge; add mTLS for partner integrations.
- Use precise CIDRs in IP allow lists (avoid wildcards).
- Log/trace with x-request-id end-to-end.
🙌 Contributing
PRs welcome!
npm ci
npm run buildOpen a PR with what changed and how to test it.
📄 License
This library is published under MIT.
© 2025 prokodo. Visit us at prokodo.com.
