Last active
July 8, 2024 14:56
-
-
Save stigok/57d075c1cf2a609cb758898c0b202428 to your computer and use it in GitHub Desktop.
Verify GitHub webhook signature header in Node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Verify GitHub webhook signature header in Node.js | |
* Written by stigok and others (see gist link for contributor comments) | |
* https://gist.github.com/stigok/57d075c1cf2a609cb758898c0b202428 | |
* Licensed CC0 1.0 Universal | |
*/ | |
const crypto = require('crypto') | |
const express = require('express') | |
const bodyParser = require('body-parser') | |
const secret = CHANGE_ME; | |
// For these headers, a sigHashAlg of sha1 must be used instead of sha256 | |
// GitHub: X-Hub-Signature | |
// Gogs: X-Gogs-Signature | |
const sigHeaderName = 'X-Hub-Signature-256' | |
const sigHashAlg = 'sha256' | |
const app = express() | |
// Saves a valid raw JSON body to req.rawBody | |
// Credits to https://stackoverflow.com/a/35651853/90674 | |
app.use(bodyParser.json({ | |
verify: (req, res, buf, encoding) => { | |
if (buf && buf.length) { | |
req.rawBody = buf.toString(encoding || 'utf8'); | |
} | |
}, | |
})) | |
function verifyPostData(req, res, next) { | |
if (!req.rawBody) { | |
return next('Request body empty') | |
} | |
const sig = Buffer.from(req.get(sigHeaderName) || '', 'utf8') | |
const hmac = crypto.createHmac(sigHashAlg, secret) | |
const digest = Buffer.from(sigHashAlg + '=' + hmac.update(req.rawBody).digest('hex'), 'utf8') | |
if (sig.length !== digest.length || !crypto.timingSafeEqual(digest, sig)) { | |
return next(`Request body digest (${digest}) did not match ${sigHeaderName} (${sig})`) | |
} | |
return next() | |
} | |
app.post('/', verifyPostData, function (req, res) { | |
res.status(200).send('Request body was signed') | |
}) | |
app.use((err, req, res, next) => { | |
if (err) console.error(err) | |
res.status(403).send('Request body was not signed or verification failed') | |
}) | |
app.listen(3000, () => console.log("Listening on port 3000")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying to figure this out while using Cloudflare Workers (and itty-router instead of express), anyone have any suggestions?