Github.com webhook verification
Webhooks allow you to build or set up integrations that subscribe to certain events on GitHub.com. When one of those events is triggered, GitHub sends an HTTP POST payload to the webhook’s configured URL. However, for security reasons, it is important to verify that the incoming webhook request is actually from GitHub and not from a malicious actor. This tutorial guides you through the steps to verify a GitHub.com webhook request in an Adobe App Builder action using a shared secret.
Set up Github secret in AppBuilder
-
Add secret to
.envfile:In the App Builder project’s
.envfile, add a custom key for the GitHub.com webhook secret:code language-env # Specify your secrets here # This file must not be committed to source control ... GITHUB_SECRET=my-github-webhook-secret-1234! -
Update
ext.config.yamlfile:The
ext.config.yamlfile must be updated to verify the GitHub.com webhook request.- Set the AppBuilder action
webconfiguration torawto receive the raw request body from GitHub.com. - Under
inputsin the AppBuilder action configuration, add theGITHUB_SECRETkey, mapping it to the.envfield containing the secret. The value of this key is the.envfield name prefixed with$. - Set the
require-adobe-authannotation in the AppBuilder action configuration tofalseto allow the action to be called without requiring Adobe authentication.
The resulting
ext.config.yamlfile should look something like this:code language-yaml operations: view: - type: web impl: index.html actions: actions web: web-src runtimeManifest: packages: dx-excshell-1: license: Apache-2.0 actions: github-to-jira: function: actions/generic/index.js web: 'raw' runtime: nodejs:20 inputs: LOG_LEVEL: debug GITHUB_SECRET: $GITHUB_SECRET annotations: require-adobe-auth: false final: true - Set the AppBuilder action
Add verification code to AppBuilder action
Next, add the JavaScript code provided below (copied from GitHub.com’s documentation) to your AppBuilder action. Make sure to export the verifySignature function.
// src/dx-excshell-1/actions/generic/github-webhook-verification.js
let encoder = new TextEncoder();
async function verifySignature(secret, header, payload) {
let parts = header.split("=");
let sigHex = parts[1];
let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };
let keyBytes = encoder.encode(secret);
let extractable = false;
let key = await crypto.subtle.importKey(
"raw",
keyBytes,
algorithm,
extractable,
[ "sign", "verify" ],
);
let sigBytes = hexToBytes(sigHex);
let dataBytes = encoder.encode(payload);
let equal = await crypto.subtle.verify(
algorithm.name,
key,
sigBytes,
dataBytes,
);
return equal;
}
function hexToBytes(hex) {
let len = hex.length / 2;
let bytes = new Uint8Array(len);
let index = 0;
for (let i = 0; i < hex.length; i += 2) {
let c = hex.slice(i, i + 2);
let b = parseInt(c, 16);
bytes[index] = b;
index += 1;
}
return bytes;
}
module.exports = { verifySignature };
Implement verification in AppBuilder action
Next, verify that the request is coming from GitHub by comparing the signature in the request header to the signature generated by the verifySignature function.
In the AppBuilder action’s index.js, add the following code to the main function:
// src/dx-excshell-1/actions/generic/index.js
const { verifySignature } = require("./github-webhook-verification");
...
// Main function that will be executed by Adobe I/O Runtime
async function main(params) {
// Create a Logger
const logger = Core.Logger("main", { level: params?.LOG_LEVEL || "info" });
try {
// Log parameters if LOG_LEVEL is 'debug'
logger.debug(stringParameters(params));
// Define required parameters and headers
const requiredParams = [
// Verifies the GITHUB_SECRET is present in the action's configuration; add other parameters here as needed.
"GITHUB_SECRET"
];
const requiredHeaders = [
// Require the x-hub-signature-256 header, which GitHub.com populates with a sha256 hash of the payload
"x-hub-signature-256"
];
// Check for missing required parameters and headers
const errorMessage = checkMissingRequestInputs(params, requiredParams, requiredHeaders);
if (errorMessage) {
// Return and log client errors
return errorResponse(400, errorMessage, logger);
}
// Decode the request body (which is base64 encoded) to a string
const body = Buffer.from(params.__ow_body, 'base64').toString('utf-8');
// Verify the GitHub webhook signature
const isSignatureValid = await verifySignature(
params.GITHUB_SECRET,
params.__ow_headers["x-hub-signature-256"],
body
);
if (!isSignatureValid) {
// GitHub signature verification failed
return errorResponse(401, "Unauthorized", logger);
} else {
logger.debug("Signature verified");
}
// Parse the request body as JSON so its data is useful in the action
const githubParams = JSON.parse(body) || {};
// Optionally, merge the GitHub webhook request parameters with the action parameters
const mergedParams = {
...params,
...githubParams
};
// Do work based on the GitHub webhook request
doWork(mergedParams);
return {
statusCode: 200,
body: { message: "GitHub webhook received and processed!" }
};
} catch (error) {
// Log any server errors
logger.error(error);
// Return with 500 status code
return errorResponse(500, "Server error", logger);
}
}
Configure webhook in GitHub
Back in GitHub.com, provide the same secret value to GitHub.com when creating the webhook. Use the secret value specified in your .env file’s GITHUB_SECRET key.
In GitHub.com, go to the repository settings, and edit the webhook. In the webhook settings, provide the secret value in the Secret field. Click Update webhook at the bottom to save the changes.
By following these steps, you ensure that your App Builder action can securely verify that incoming webhook requests are indeed from your GitHub.com webhook.