Blog

Bauen Sie Ihren ersten Slack-Bot mit Block Kit - Eine Schritt-für-Schritt-Anleitung

Simon Karman

Aktualisiert Oktober 15, 2025
16 Minuten

Vor kurzem hat Xebia einen neuen Bürostandort in Amsterdam eröffnet. An diesem Standort gibt es nur eine begrenzte Anzahl von Parkplätzen, und wir möchten vermeiden, dass Kollegen mit ihrem Auto im Büro ankommen, ohne einen freien Parkplatz zu haben. Deshalb habe ich die Aufgabe übernommen, einen Slack-Bot zu entwickeln, der das Anzeigen, Stornieren und Buchen von Parkplätzen in der Garage erleichtern sollte.

In diesem Artikel möchte ich Ihnen zeigen, was ich beim Bau dieses Slack-Bots gelernt habe. Ich habe den Slack-Bot mit Block Kit erstellt. Block Kit ist das saubere und konsistente UI-Framework für Slack-Apps. In diesem Blogbeitrag erfahren Sie, wie Sie in diesen fünf Schritten einen Slack Bot mit Block Kit erstellen:

  1. Einrichten einer Slack-App
  2. Erstellen eines Handlers, der Slack-Befehle und Interaktivität verwaltet
  3. Erstellen eines Slack UI-Layouts mit dem Block Kit Builder
  4. Ihr Block-Kit-Layout anzeigen
  5. Interaktivität zu Ihrem Blocksatz-Layout hinzufügen

Nach der Lektüre dieses Blogbeitrags sollten Sie wissen, wie Sie mit Block Kit Ihren eigenen Slack-Bot erstellen können.

1. Einrichten einer Slack-App

Zunächst müssen Sie eine neue Slack-App erstellen. Sie können eine neue Slack App hier erstellen. Wählen Sie 'Neue App erstellen' > 'Von Grund auf' und geben Sie einen App-Namen und den Slack-Arbeitsbereich an, in dem Sie diese App entwickeln möchten.

Einrichten einer Slack-App über das Slack API-Webportal

Einrichten einer Slack-App über das Slack-API-Webportal.

Nachdem Sie Ihre Slack App erstellt haben, navigieren Sie zur Registerkarte 'Grundlegende Informationen' Ihrer App und klicken Sie auf 'In Arbeitsbereich installieren'. Damit können Sie Ihre App in Ihrem Arbeitsbereich testen und die API-Token generieren, die Sie später benötigen.

2. Erstellen eines Handlers, der Slack-Befehle und Interaktivität verwaltet

Als nächstes werden Sie einen Handler für Ihren Bot erstellen. Außerdem müssen Sie Slack so konfigurieren, dass dieser Handler immer dann aufgerufen wird, wenn ein Befehl an den Bot gesendet wird oder eine Interaktivität mit dem Bot stattfindet. In diesem Blogpost wird eine einzige Lambda-Funktion verwendet, um sowohl eingehende Befehle als auch eingehende Interaktivität zu verarbeiten.

Slack zu Lambda

Slack API erreicht AWS Lambda.

2.1 Erstellen Ihres Handlers mit einer AWS Lambda-Funktion

In diesem Beispiel verwende ich eine Node.js AWS Lambda-Funktion, um den Handler zu hosten. Sie können auch andere Programmiersprachen oder Hosting-Umgebungen verwenden, solange Sie einen öffentlichen HTTP-Endpunkt haben, der von Slack erreicht werden kann.

Um die AWS Lambda-Funktion zu erstellen, navigieren Sie zu AWS Lambda in der AWS-Konsole. Klicken Sie dann auf 'Funktion erstellen', geben Sie Ihrer Funktion einen passenden Namen und wählen Sie die Node.js 20.x Laufzeit. Klicken Sie dann auf 'Funktion erstellen'.

Hinweis: Für die Bereitstellung Ihrer Ressourcen in AWS empfehlen wir die Verwendung von Infrastructure as Code (IaC) Tools, wie Terraform, Cloud Formation oder AWS CDK. Diese Tools machen die manuelle Erstellung von Ressourcen überflüssig und sorgen für Konsistenz.

Slack verlangt, dass Ihr Handler öffentlich über HTTP erreichbar ist. Innerhalb von AWS Lambda können Sie dies erreichen, indem Sie eine URL für Ihre Lambda-Funktion einrichten.

  1. Öffnen Sie in Ihrer Lambda-Funktion in der AWS-Konsole die Registerkarte 'Konfiguration'.
  2. Klicken Sie auf der linken Seite auf 'Funktions-URL' und dann auf 'Funktions-URL erstellen'.
  3. Wählen Sie 'Keine' für den Authentifizierungstyp. Die Authentifizierung Ihres Bots wird im Handler selbst vorgenommen.
  4. Und klicken Sie auf 'Speichern'. Jetzt ist Ihre Funktions-URL erstellt. Sie sollte in etwa wie folgt aussehen: https://abcdefghijklmnopqrstuvwxyz0123.lambda-url.eu-west-1.on.aws/
  5. Kopieren Sie die URL der Funktion. Diese benötigen Sie im folgenden Schritt.

2.2 Konfigurieren der Slack-App zum Aufrufen Ihres Handlers

Als nächstes müssen Sie Slack so konfigurieren, dass Ihr Handler (URL-Endpunkt) aufgerufen wird, sobald ein Befehl an den Bot gesendet wird oder eine Interaktion mit dem Bot stattfindet. Sie können Ihre Slack-App hier konfigurieren.

Beginnen wir mit der Einrichtung des Befehls-Handlers. Navigieren Sie im Menü auf der linken Seite zum Abschnitt 'Slash-Befehle'. Klicken Sie dann auf 'Neuen Befehl erstellen'. Geben Sie Ihrem Befehl einen passenden Namen (z.B. /my-bot) und verwenden Sie im Feld 'URL anfordern' den URL-Endpunkt Ihres Handlers.

Wenn Sie jetzt in Slack /my-bot in einem beliebigen Kanal oder privaten Chat posten, sollte Ihr Bot darauf antworten. Vorerst wird er mit der Nachricht "Hallo von Lambda!" antworten. Die Antwort des Bots wird nur für Sie sichtbar sein.

Die Hallo, Welt des Slack-Bots

Das "Hallo, Welt" des Slack Bot.

Derzeit ist die Benutzeroberfläche nicht interaktiv. Sie besteht nur aus einem Stück Text. Sobald Sie anfangen, mit der Benutzeroberfläche Ihres Bots zu interagieren, sendet Slack Interaktivitätsereignisse aus. Lassen Sie uns Ihre Slack App so konfigurieren, dass sie diese Interaktivitätsereignisse an Ihren Handler sendet.

Suchen Sie Ihre Slack-App. Navigieren Sie dann zum Abschnitt "Interaktivität & Shortcuts" im Menü auf der linken Seite. Aktivieren Sie in der oberen rechten Ecke dieses Abschnitts die Interaktivität. Schließlich geben Sie den URL-Endpunkt Ihres Handlers als 'Request URL' an.

Jetzt werden alle Interaktionen mit Verknüpfungen, Modals oder interaktiven Komponenten (wie Schaltflächen, Auswahlmenüs und Datumsauswahlen) ebenfalls als HTTP POST-Anfragen an Ihren Handler gesendet.

3. Erstellen Sie eine Slack-Benutzeroberfläche mit dem Block Kit Builder

Block Kit ist das saubere und konsistente UI-Framework für Slack-Apps. Sie können damit visuell reichhaltige und interaktive Nachrichten entwerfen, indem Sie ein Layout aus einzelnen Blöcken zusammenstellen. Blöcke sind visuelle Komponenten, die gestapelt und angeordnet werden können, um App-Layouts zu erstellen. Einige Komponenten (z. B. Schaltflächen) sorgen für Interaktivität in Ihrem Layout. Einen Überblick über die verfügbaren Blöcke finden Sie in der API-Referenz der Block Kit Blöcke.

Im Block Kit Builder können Sie Ihre Komponenten entwerfen. Dazu ziehen Sie die Blöcke aus der linken Spalte per Drag & Drop direkt in die Vorschau. Ein JSON-Beispiel für die Blöcke wird auf der rechten Seite angezeigt. Sie können das JSON-Beispiel auch direkt bearbeiten, damit die Änderungen in der Vorschau angezeigt werden.

Lassen Sie uns das folgende Layout erstellen.

Ein Layout, das einen Zähler im Block Kit Builder anzeigt.

Ein Layout, das einen Zähler im Block Kit Builder anzeigt.

Das Layout stellt einen Zähler dar und besteht aus 2 Blöcken. Einem 'Abschnitt'-Block, der etwas Text anzeigt, darunter den Benutzernamen. Und einem 'Aktionen'-Block, der drei Schaltflächenelemente (-1, 0, 1) anzeigt.

Klicken Sie hier, um das JSON für diese Komponente zu sehen. Wenn Sie Anpassungen vornehmen möchten, kopieren Sie das JSON und fügen es in den Block Kit Builder ein.
{
  "blocks": [
    {
      "type":"section",
      "text": {
        "type":"mrkdwn",
        "text":"Hi, *Simon* -- Keep track of a counter."
      }
    },
    {
      "type":"actions",
      "block_id":"counter",
      "elements": [
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"-1"
          },
          "value":"-1",
          "action_id":"decrease"
        },
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"0"
          },
          "action_id":"reset"
        },
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"+1"
          },
          "value":"1",
          "action_id":"increase"
        }
      ]
    }
  ]
}

Interaktive Blöcke in Ihrem Layout haben zusätzliche Details wie block_id, action_id und value. Diese sind nicht sichtbar und werden nur verwendet, wenn ein Benutzer mit diesen Blöcken interagiert. Die entsprechenden Werte der block_id, action_id und value werden zusammen mit dem Interaktivitätsereignis gesendet, wenn ein Benutzer mit Ihrer App interagiert. So können Sie feststellen, welche Aktion der Benutzer mit seiner Interaktion erreichen wollte.

Wenn Sie mit Ihrem Layout zufrieden sind. Speichern Sie das JSON, Sie werden es im nächsten Schritt benötigen.

4. Ihr Block-Kit-Layout anzeigen

4.1 Aktualisieren des Handlers

Lassen Sie uns nun Ihr Block Kit Layout anzeigen, wenn ein Benutzer den Befehl /my-bot aufruft. Dazu müssen Sie das Layout als JSON aus dem Handler zurückgeben.

In einem Node.js AWS Lambda-Funktionshandler können Sie dazu den folgenden Code verwenden.

const myLayout = '/* paste your layout here! */';

export const handler = async (event) => {
  return { statusCode: 200, body: JSON.stringify(myLayout) };
};

Versuchen Sie nun, Ihren /my-bot Befehl erneut auszuführen. Sie sollten sehen, dass Ihr Layout in Slack erscheint.

4.2 Anpassen der Antwort

Derzeit wird unabhängig davon, wer den Bot aufgerufen hat, immer Hi, Simon angezeigt, da dieser Text im Layout fest kodiert ist. Der Handler muss das Layout, das er zurückgibt, entsprechend dem Benutzer, der den Slack-Bot aufgerufen hat, aktualisieren.

Wenn ein Slash-Befehl aufgerufen wird, sendet Slack einen HTTP POST an den Handler. Diese Anfrage enthält einen Dateninhalt, der Informationen über den Befehl enthält, darunter auch, wer ihn aufgerufen hat. Beachten Sie, dass diese Daten mit einem Content-Type Header gesendet werden, der auf application/x-www-form-urlencoded eingestellt ist. Einzelheiten zu einigen wichtigen Feldern, die in dieser Nutzlast enthalten sein können, finden Sie in der Slash Command Documentation.

Eines der Felder in der Nutzlast ist user_name. Sie können den Wert dieses Feldes verwenden, um das vom Handler zurückgegebene Layout zu aktualisieren.

4.2.a Extrahieren des Benutzernamens

In AWS Lambda wird das Ereignis als erstes Argument an den Handler übergeben. Im Code unten sehen Sie, wie der Ereigniskörper extrahiert und dekodiert wird. Anschließend wird aus diesen Daten die user_name extrahiert und zur Aktualisierung des entsprechenden Blocks im Layout verwendet.

const myLayout = '/* paste your layout here! */';

export const handler = async (event) => {
  // Decode the body of the event to be able to extract the information from Slack
  const data = new URLSearchParams(Buffer.from(event.body, 'base64').toString());

  // Extract the username and update the block
  const username = data.get("user_name");
  myLayout.blocks[0].text.text = `Hi, *${username}* -- Keep track of a counter.`;

  // Return the block
  return { statusCode: 200, body: JSON.stringify(myLayout) };
};

4.2.b Sichern Ihres Handlers

Derzeit kann jeder, der Ihre Endpunkt-URL kennt, sie mit jeder beliebigen Nutzlast aufrufen. Sie können dies selbst ausprobieren: curl -X POST <your-handler-url> und sehen, was passiert.

Für unser einfaches Beispiel stellt dies kein großes Problem dar. Wenn Sie jedoch Datenbankaufrufe oder andere Geschäftslogik in Ihrem Slack-Handler durchführen, möchten Sie wahrscheinlich nicht, dass jemand (außer Slack) diese aufrufen kann.

Slack signiert seine Anfragen mit einem für Ihre App einzigartigen Geheimnis. Mit Hilfe von signierten Geheimnissen kann Ihre App sicherer überprüfen, ob Anfragen von Slack authentisch sind. Weitere Informationen finden Sie in dem Artikel Verifizierung von Anfragen von Slack.

Um diese Funktion zum Handler hinzuzufügen, können Sie den unten stehenden Codeschnipsel kopieren und ihn in die Datei verify.mjs Ihres Handlers einfügen.

Datei: verify.mjs (Klicken Sie, um den Inhalt der Datei anzuzeigen)
// create verify.mjs
import { createHmac } from 'crypto';

const verifyErrorPrefix = 'Failed to verify authenticity';

/**
 * Verifies the signature of an incoming request from Slack.
 * If the request is invalid, this method throws an exception with the error details.
 */
export function verifySlackRequest(options) {
  const requestTimestampSec = options.headers['x-slack-request-timestamp'];
  const signature = options.headers['x-slack-signature'];
  if (Number.isNaN(requestTimestampSec)) {
    throw new Error(
      `Failed to verify authenticity: header x-slack-request-timestamp did not have the expected type (${requestTimestampSec})`,
    );
  }

  // Calculate time-dependent values
  const nowMs = Date.now();
  const requestTimestampMaxDeltaMin = 5;
  const fiveMinutesAgoSec = Math.floor(nowMs / 1000) - 60 * requestTimestampMaxDeltaMin;

  // Enforce verification rules

  // Rule 1: Check staleness
  if (requestTimestampSec < fiveMinutesAgoSec) {
    throw new Error(`${verifyErrorPrefix}: x-slack-request-timestamp must differ from system time by no more than ${requestTimestampMaxDeltaMin
    } minutes or request is stale`);
  }

  // Rule 2: Check signature
  // Separate parts of signature
  const [signatureVersion, signatureHash] = signature.split('=');
  // Only handle known versions
  if (signatureVersion !== 'v0') {
    throw new Error(`${verifyErrorPrefix}: unknown signature version`);
  }
  // Compute our own signature hash
  const hmac = createHmac('sha256', options.signingSecret);
  hmac.update(`${signatureVersion}:${requestTimestampSec}:${options.body}`);
  const ourSignatureHash = hmac.digest('hex');
  if (!signatureHash || signatureHash !== ourSignatureHash) {
    throw new Error(`${verifyErrorPrefix}: signature mismatch`);
  }
}

/**
 * Verifies the signature of an incoming request from Slack.
 * If the request is invalid, this method returns false.
 */
export function isValidSlackRequest(options) {
  try {
    verifySlackRequest(options);
    return true;
  } catch (e) {
    console.warn(`Signature verification error: ${e}`);
  }
  return false;
}

Dann rufen Sie in Ihrem Handler die Methode isValidSlackRequest aus der Datei verify.mjs auf. Vergessen Sie nicht, die Anweisung import am Anfang der Datei hinzuzufügen.

import { isValidSlackRequest } from './verify.mjs';

export const handler = async (event) => {
  // Validate the Slack signature <---- here
  const body = Buffer.from(event.body, 'base64').toString();
  if (!isValidSlackRequest({
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    body,
    headers: event.headers,
  })) {
    return { statusCode: 403 };
  }

  // ... keep the rest of your handler the same ...
};

Verwenden Sie schließlich das Signing Secret aus den Details Ihrer Slack-Anwendung und fügen Sie damit eine Umgebungsvariable für Ihren AWS Lambda mit folgendem Namen hinzu: SLACK_SIGNING_SECRET.

Ihre Anfragen von Slack aus sollten jetzt noch korrekt funktionieren (versuchen Sie, den Befehl /my-app erneut auszuführen). Wenn Sie jedoch versuchen, benutzerdefinierte Daten direkt an die Endpunkt-URL zu POSTen, sollten Sie eine Forbidden (HTTP 403) Antwort erhalten.

5. Hinzufügen von Interaktivität zu Ihrem Blocksatz-Layout

Es ist an der Zeit, die Slack-App interaktiv zu machen, indem Sie sicherstellen, dass der Handler die Interaktionen der Schaltflächen verarbeitet. Interaktivitätsereignisse werden anders behandelt als Slash-Befehlsereignisse. Erstens werden die Ereignisinformationen als JSON-Objekt im Feld payload des Body übergeben. Zweitens müssen Sie, um Informationen an den Benutzer zurückzusenden, die Daten an response_url senden, anstatt sie im Body der Antwort zurückzugeben.

Im Code unten sehen Sie, wie der Ereigniskörper extrahiert und dekodiert wird. Wenn diese Daten ein payload enthalten, zeigt Ihnen das, dass Sie Interaktivität behandeln. Anstatt mit den Layoutblöcken zu reagieren, kann Ihr Handler die Methode handleInteractivity mit der dekodierten JSON-Nutzlast aufrufen, wie im Codebeispiel gezeigt.

const myLayout = '/* paste your layout here! */';

const handleInteractivity = async (payload) => {
  // ... more later ...
};

export const handler = async (event) => {
  // Decode the body of the event to be able to extract the information from Slack
  const data = new URLSearchParams(Buffer.from(event.body, 'base64').toString());

  // Check if this is an interactivity event <----- here
  if (data.has("payload")) {
    await handleInteractivity(JSON.parse(data.get("payload")));
    return { statusCode: 200 };
  }

  // Extract the username and update the block
  const username = data.get("user_name");
  myLayout.blocks[0].text.text = `Hi, *${username}* -- Keep track of a counter.`;

  // Return the block
  return { statusCode: 200, body: JSON.stringify(myLayout) };
};

Mit der Methode handleInteractivity können Sie ein neues Layout erstellen. Das neue Layout, das Sie erstellen, ähnelt der Interaktivität oder Änderung, die gerade gesendet wurde. So können Sie beispielsweise die Werte der Zählertasten auf der Grundlage des Nutzdatenwerts aktualisieren.

Um das Layout nach einer Interaktivität zu aktualisieren, müssen Sie die response_url verwenden, die Teil der Nutzlast ist. Jedes JSON, das Sie an diese response_url senden, bestimmt das Verhalten dieser Interaktivität.

Im folgenden Beispiel wird ein aktualisiertes Layout an die Antwort-URL POST-ed. Da das Feld replace_original im Layout auf true gesetzt ist, wird Slack die ursprüngliche Nachricht, mit der der Benutzer in Slack interagiert hat, durch das neue Layout ersetzen.

const handleInteractivity = async (payload) => {  
  // Create a clone of the layout to avoid updating the original layout 
  const layout = structuredClone(myLayout);

  // Ensure this layout replaces the existing message
  layout["replace_original"] = true;

  // Extract the username and update the block
  const username = payload["user"]["username"];
  layout.blocks[0].text.text = `Hi, *${username}* -- Keep track of a counter.`;

  // Find the value associated to this action and update the buttons
  const value = parseInt(payload["actions"][0].value, 10);
  layout.blocks[1].elements[0].value = (value - 1).toString();
  layout.blocks[1].elements[1].text.text = value.toString();
  layout.blocks[1].elements[1].value = value.toString();
  layout.blocks[1].elements[2].value = (value + 1).toString();

  // Post the layout to the response url
  await fetch(payload["response_url"], {
    method: 'POST',
    body: JSON.stringify(layout),
    headers: { 'Content-Type': 'application/json' }
  });
};

Nachdem Sie Ihren Handler mit dem Interaktivitätscode aktualisiert haben, sollte Ihre App wie folgt aussehen. Durch Drücken der Tasten -1 und +1 wird der Zähler erhöht bzw. verringert.

Der Zähler funktioniert!

Fazit

Sie haben gesehen, wie Sie eine neue Slack-App erstellen, einen Handler für Ihren Bot erstellen, ein interaktives Layout mit Block Kit erstellen und Ihren Handler die Funktionalität Ihres interaktiven Layouts für Ihre Benutzer bereitstellen lassen.

Es liegt nun an Ihnen, Ihre eigene Slack-App zu erstellen und den Handler nützliche Dinge für Ihr Unternehmen tun zu lassen. Denken Sie daran, dass Ihr Handler alles Mögliche ausführen kann, z. B. Datenbankabfragen oder den Aufruf von APIs, so dass die Möglichkeiten Ihrer Slack-App endlos sind.

Wenn Sie an mehr interessiert sind, dann werfen Sie einen Blick auf Block Builder, eine Node.js-Bibliothek zur Erstellung von Slack Block Kit UIs, und Bolt.js, ein Framework zur Erstellung von Slack-Apps mit JavaScript. Diese beiden Bibliotheken helfen Ihnen dabei, einen ausgefeilten Slack-Bot in großem Maßstab zu erstellen.

Vollständiges Code-Beispiel

Der vollständige Code des Beispiel-Handlers aus diesem Blog ist unten verfügbar. Beachten Sie, dass es zwei Dateien gibt index.mjs und verify.mjs und dass Sie Ihre Umgebungsvariable für die SLACK_SIGNING_SECRET hinzufügen sollten.

Datei: index.mjs (zum Anzeigen klicken)
// in index.mjs
import { isValidSlackRequest } from './verify.mjs';

const myLayout = {
  "blocks": [
    {
      "type":"section",
      "text": {
        "type":"mrkdwn",
        "text":"Keep track of a counter."
      }
    },
    {
      "type":"actions",
      "block_id":"counter",
      "elements": [
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"-1"
          },
          "value":"-1",
          "action_id":"decrease"
        },
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"0"
          },
          "action_id":"reset"
        },
        {
          "type":"button",
          "text": {
            "type":"plain_text",
            "text":"+1"
          },
          "value":"1",
          "action_id":"increase"
        }
      ]
    }
  ]
};

const handleInteractivity = async (payload) => {
  const layout = structuredClone(myLayout);

  // Ensure this layout replaces the existing message
  layout["replace_original"] = true;

  // Extract the username and update the block
  const username = payload["user"]["username"];
  layout.blocks[0].text.text = `Hi, *${username}* -- Keep track of a counter.`;

  // Find the value associated to this action and update the buttons
  const value = parseInt(payload["actions"][0].value, 10);
  layout.blocks[1].elements[0].value = (value - 1).toString();
  layout.blocks[1].elements[1].text.text = value.toString();
  layout.blocks[1].elements[1].value = value.toString();
  layout.blocks[1].elements[2].value = (value + 1).toString();

  // Post the layout to the response url
  const fetchDetails = [payload["response_url"], { method: 'POST', body: JSON.stringify(layout), headers: { 'Content-Type': 'application/json' } }];
  const response = await fetch(...fetchDetails);
  console.info('Responded!', response, fetchDetails);
};

export const handler = async (event) => {
  // Validate the Slack signature
  const body = Buffer.from(event.body, 'base64').toString();
  if (!isValidSlackRequest({
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    body,
    headers: event.headers,
  })) {
    return { statusCode: 403 };
  }

  // Decode the body of the event to be able to extract the information from Slack
  const data = new URLSearchParams(body);

  // Check if this is an interactivity event
  if (data.has("payload")) {
    await handleInteractivity(JSON.parse(data.get("payload")));
    return { statusCode: 200 };
  }

  // Extract the username and update the block
  const username = data.get("user_name");
  myLayout.blocks[0].text.text = `Hi, *${username}* -- Keep track of a counter.`;

  // Return the block
  return { statusCode: 200, body: JSON.stringify(myLayout) };
};
Datei: verify.mjs (zum Anzeigen klicken)
// in verify.mjs
import { createHmac } from 'crypto';

const verifyErrorPrefix = 'Failed to verify authenticity';

/**
 * Verifies the signature of an incoming request from Slack.
 * If the request is invalid, this method throws an exception with the error details.
 */
export function verifySlackRequest(options) {
  const requestTimestampSec = options.headers['x-slack-request-timestamp'];
  const signature = options.headers['x-slack-signature'];
  if (Number.isNaN(requestTimestampSec)) {
    throw new Error(
      `Failed to verify authenticity: header x-slack-request-timestamp did not have the expected type (${requestTimestampSec})`,
    );
  }

  // Calculate time-dependent values
  const nowMs = Date.now();
  const requestTimestampMaxDeltaMin = 5;
  const fiveMinutesAgoSec = Math.floor(nowMs / 1000) - 60 * requestTimestampMaxDeltaMin;

  // Enforce verification rules

  // Rule 1: Check staleness
  if (requestTimestampSec < fiveMinutesAgoSec) {
    throw new Error(`${verifyErrorPrefix}: x-slack-request-timestamp must differ from system time by no more than ${requestTimestampMaxDeltaMin
    } minutes or request is stale`);
  }

  // Rule 2: Check signature
  // Separate parts of signature
  const [signatureVersion, signatureHash] = signature.split('=');
  // Only handle known versions
  if (signatureVersion !== 'v0') {
    throw new Error(`${verifyErrorPrefix}: unknown signature version`);
  }
  // Compute our own signature hash
  const hmac = createHmac('sha256', options.signingSecret);
  hmac.update(`${signatureVersion}:${requestTimestampSec}:${options.body}`);
  const ourSignatureHash = hmac.digest('hex');
  if (!signatureHash || signatureHash !== ourSignatureHash) {
    throw new Error(`${verifyErrorPrefix}: signature mismatch`);
  }
}

/**
 * Verifies the signature of an incoming request from Slack.
 * If the request is invalid, this method returns false.
 */
export function isValidSlackRequest(options) {
  try {
    verifySlackRequest(options);
    return true;
  } catch (e) {
    console.warn(`Signature verification error: ${e}`);
  }
  return false;
}

Verfasst von

Simon Karman

Simon Karman is a cloud engineer and consultant that builds effective cloud solutions ranging from serverless applications, to apis, to CICD pipelines, and to infrastructure as code solutions in both AWS and GCP.

Contact

Let’s discuss how we can support your journey.