📝 はじめに
複数のプロジェクトでSlack連携を実装していると、いつの間にか「あれ、このトークンは何に使うんだっけ?」「WebhookとBotの違いって?」と混乱してしまうことがあります。
この記事では、Slack連携で使用する認証情報を通信の方向という観点から整理し、それぞれの役割と使い分けを解説します。
🎯 通信方向で理解するSlack認証
Slack連携の認証情報は、データの流れる方向によって必要なものが変わります。
📊 認証情報マップ
| 通信の方向 | 必要な認証情報 | 用途 | 使用場面 |
|---|---|---|---|
| 🚀 Slackへ送信 | SLACK_BOT_TOKEN |
Slack API呼び出し認証 | エラー通知、イベント報告 |
| 🔒 Slackから受信 | SLACK_SIGNING_SECRET |
Slackからのリクエスト署名検証 | Events API、ボタン操作 |
| 🌐 外部から受信 | WEBHOOK_SECRET |
カスタムWebhook認証 | Discord等からの投稿 |
| 📍 送信先指定 | SLACK_*_CHANNEL |
通知先チャンネルID | どこに送るか指定 |
この表を頭に入れておくと、「今実装しようとしている機能は、どの方向の通信なのか?」を考えるだけで、必要な認証情報が分かります。
🔑 認証情報の詳細解説
1. SLACK_BOT_TOKEN - Slackへ送信する鍵
これは何?
- Slack APIを呼び出すための認証トークン
xoxb-で始まる文字列(例:xoxb-123456-789012-abcdefg)
どこで取得?
- Slack App設定ページを開く
- 「OAuth & Permissions」セクション
- 「Bot User OAuth Token」をコピー
どんな時に使う?
// Slackにメッセージを送信する例
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.SLACK_BOT_TOKEN}` // ← ここで使用
},
body: JSON.stringify({
channel: 'C0XXXXX1234', // 実際のチャンネルID
text: '✨ お嬢様、処理が完了いたしました!'
})
});
セキュリティのポイント:
- ✅ 必ずSecretとして暗号化保存
- ✅ GitHubなどに直接コミットしない
- ✅ Cloudflare Dashboardの「Encrypt」チェックを有効化
2. SLACK_SIGNING_SECRET - Slackからの通信を検証する鍵
これは何?
- Slackから届いたリクエストが本物かどうか確認するためのシークレット
- ランダムな文字列
どこで取得?
- Slack App設定ページを開く
- 「Basic Information」セクション
- 「Signing Secret」をコピー
どんな時に使う?
- SlackのEvents APIでイベントを受信する時
- Interactive Components(ボタンやメニュー)からの操作を受信する時
- Slash Commandsを受信する時
検証の仕組み:
// Slackからのリクエストを検証する処理
async function verifySlackSignature(request, signingSecret, rawBody) {
const timestamp = request.headers.get('X-Slack-Request-Timestamp');
const signature = request.headers.get('X-Slack-Signature');
// 1. タイムスタンプが5分以内か確認(リプレイ攻撃対策)
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
return false;
}
// 2. HMAC-SHA256で署名を計算
const sigBasestring = `v0:${timestamp}:${rawBody}`;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(signingSecret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const hmac = await crypto.subtle.sign('HMAC', key, encoder.encode(sigBasestring));
const computedSignature = 'v0=' + Array.from(new Uint8Array(hmac))
.map(b => b.toString(16).padStart(2, '0')).join('');
// 3. Slackが送信した署名と一致するか確認
return signature === computedSignature;
}
なぜ必要?
- 悪意のある第三者が「なりすまし」でリクエストを送ってくるのを防ぐ
- Slackだけが知っているシークレットで署名することで、本物のSlackからのリクエストだと証明できる
セキュリティのポイント:
- ✅ 必ずSecretとして暗号化保存
- ✅ 検証に失敗したリクエストは即座に拒否する
- ✅ タイムスタンプチェックでリプレイ攻撃を防ぐ
3. WEBHOOK_SECRET - 外部サービスからの通信を検証する鍵
これは何?
- あなたのシステムが独自に定義したWebhook用の認証トークン
- Slackとは無関係(Discord、GitHub、独自システム等からのWebhook用)
どこで取得?
- 自分で生成する(例:
openssl rand -hex 32)
どんな時に使う?
// Discord Webhookからの投稿を受け取る例
export const POST: APIRoute = async ({ request, locals }) => {
const runtime = (locals as any).runtime;
// Authorization ヘッダーでトークンを検証
const authHeader = request.headers.get('Authorization');
const expectedToken = runtime.env.WEBHOOK_SECRET;
if (!authHeader || authHeader !== `Bearer ${expectedToken}`) {
return new Response("Unauthorized", { status: 401 }); // ← 拒否
}
// 検証OKなら処理を続行
const body = await request.json();
// ... 投稿を保存する処理 ...
};
Slackのものと何が違う?
| SLACK_SIGNING_SECRET | WEBHOOK_SECRET | |
|---|---|---|
| 誰が発行? | Slackが生成 | 自分で生成 |
| 検証方法 | HMAC-SHA256署名 | Bearerトークン比較 |
| 用途 | Slackからのイベント検証 | 外部サービスからのWebhook検証 |
セキュリティのポイント:
- ✅ 必ずSecretとして暗号化保存
- ✅ 長い(32バイト以上)ランダムな文字列を使用
- ✅ 定期的に更新する運用を検討
4. SLACK_*_CHANNEL - 通知先を指定する
これは何?
- Slackチャンネルの一意なID
Cで始まる11桁の文字列(例:C0XXXXX1234)
どこで取得?
- Slackでチャンネル名を右クリック
- 「リンクをコピー」を選択
- URLの末尾
/C0XXXXX1234の部分を抽出
種類と用途:
| 環境変数名 | 用途 | 通知内容例 |
|---|---|---|
SLACK_ERROR_CHANNEL |
エラー通知 | DB接続エラー、API呼び出し失敗 |
SLACK_AUTH_CHANNEL |
認証イベント通知 | パスキー登録成功、ログイン失敗 |
SLACK_REPORT_CHANNEL |
イベント進捗報告 | 目標ポイント達成、目標値変更通知 |
使用例:
const notifyError = async (message, env) => {
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.SLACK_BOT_TOKEN}`
},
body: JSON.stringify({
channel: env.SLACK_ERROR_CHANNEL, // ← チャンネルIDを指定
text: `🚨 エラー: ${message}`
})
});
};
セキュリティのポイント:
- ✅ 平文で
wrangler.jsoncに保存してOK - ✅ チャンネルIDだけでは投稿も閲覧もできない(ワークスペースのメンバーかつ招待済みである必要がある)
- ✅ むしろ平文にすることで設定ミスを防げる
🏗️ 実践: プロジェクト別の使い分け
私のプロジェクトでは、以下のように使い分けています。
プロジェクト構成
~/projects/
├── event-worker/ # 軍師bot(イベント管理Worker)
├── webhook-worker/ # メイドbot(つぶやき記録Worker)
└── webapp/ # 管理画面(Astro + Cloudflare Pages)
使用マトリクス
| 認証情報 | event-worker | webhook-worker | webapp |
|---|---|---|---|
| SLACK_BOT_TOKEN | ✅ | ✅ | ✅ |
| SLACK_SIGNING_SECRET | ✅ | ✅ | ❌ |
| WEBHOOK_SECRET | ❌ | ❌ | ✅ |
| SLACK_ERROR_CHANNEL | ❌ | ❌ | ✅ |
| SLACK_AUTH_CHANNEL | ❌ | ❌ | ✅ |
| SLACK_REPORT_CHANNEL | ❌ | ❌ | ✅ |
なぜこの構成?
event-worker と webhook-worker(Workers):
- Slackから直接イベントを受信 →
SLACK_SIGNING_SECRETが必要 - Slackへ通知を送信 →
SLACK_BOT_TOKENが必要 - チャンネルIDは固定(コード内に直接記述)
webapp(Pages):
- ユーザー操作や外部Webhookを受信 →
WEBHOOK_SECRETが必要 - Slackへ通知を送信 →
SLACK_BOT_TOKENが必要 - Slackから直接イベントは受けない →
SLACK_SIGNING_SECRETは不要 - 複数チャンネルに通知 →
SLACK_*_CHANNELで柔軟に切り替え
🎭 設計思想: Bot の役割分担
このプロジェクトでは、2つの異なるBotを使い分けています。
軍師bot(event-worker)
役割: イベント進捗管理・戦略立案
担当機能:
- スクショからイベントポイント読み取り
- 日課達成の判定と祝電送信提案
- 進軍予定表の作成
- 戦術ごとの必要リソース計算
- 推奨戦術の提案
- 目標値変更の通知
キャラクター: 忠実で知的な軍師 / 口調「〜ですぞ」「〜でございます」
メイドbot(webhook-worker + webapp)
役割: お屋敷(システム)の管理・記録
担当機能:
- つぶやきの記録
- 認証イベントの通知
- エラー監視と報告
キャラクター: 執事のような丁寧なメイド / 口調「〜ですわ」「〜でございます」
なぜ分ける?
通知の意図が明確になる
- 「軍師から通知 → イベント関連」「メイドから通知 → システム管理関連」と一目で分かる
機能の拡張がしやすい
- 軍師botに新しい戦略支援機能を追加しても、メイドbotには影響しない
トークンの使い分けが明確
- 管理画面(webapp)で目標値を変更した場合も、「イベント関連」なので軍師botのトークンで通知
- エラー発生時は「システム管理」なのでメイドbotのトークンで通知
💡 よくある質問と答え
Q1. 管理画面(webapp)から軍師botのトークンを使うのはなぜ?
A: イベント関連の通知はすべて「軍師の担当」という設計のため。管理画面でユーザーが目標値を変更した際も、Slackには軍師として報告するのが自然です。
// webapp/src/pages/admin/admin-dashboard.astro より
const token = runtime.env.EVENT_WORKER_BOT_TOKEN; // ← 軍師botのトークン
await fetch('https://slack.com/api/chat.postMessage', {
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({
channel: runtime.env.SLACK_REPORT_CHANNEL,
text: '⚔️ *軍師の進言* ⚔️\n目標値が変更されました…'
})
});
Q2. event-worker Worker と webapp Pages は別のプロジェクトなのに、同じトークンを使っていい?
A: むしろ推奨です。同じSlack Appのトークンを共有することで、「軍師bot」として一貫したアイデンティティで通知できます。
Cloudflareの環境変数はプロジェクトごとに独立しているため、以下のように両方に設定します:
# event-worker Worker
npx wrangler secret put SLACK_BOT_TOKEN
# webapp Pages
# Cloudflare Dashboard から EVENT_WORKER_BOT_TOKEN として同じ値を登録
Q3. Workerには SLACK_SIGNING_SECRET があるのに、Pagesにはないのはなぜ?
A: Workers(event-worker/webhook-worker)はSlackから直接イベントを受信するので Signing Secret が必要です。Pagesはユーザー操作や外部Webhookを受信するのみで、Slackからの直接イベントは受けないため不要です。
| プロジェクト | Slackからイベント受信 | SLACK_SIGNING_SECRET |
|---|---|---|
| event-worker (Worker) | ✅ する | ✅ 必要 |
| webhook-worker (Worker) | ✅ する | ✅ 必要 |
| webapp (Pages) | ❌ しない | ❌ 不要 |
Q4. チャンネルIDを平文で wrangler.jsonc に書いて大丈夫?
A: 問題ありません。チャンネルIDだけでは投稿も閲覧もできません(ワークスペースのメンバーかつ招待済みである必要があります)。
むしろ平文にすることで以下のメリットがあります:
- ✅ 設定ミスに気づきやすい(typoしたらすぐわかる)
- ✅ GitHubでレビューしやすい(チャンネルIDの変更履歴が残る)
- ✅ Dashboardで確認しやすい(どのチャンネルに通知しているか一目瞭然)
// wrangler.jsonc
{
"vars": {
"SLACK_ERROR_CHANNEL": "C0XXXXX1234", // #errors
"SLACK_AUTH_CHANNEL": "C0XXXXX5678", // #auth-events
"SLACK_REPORT_CHANNEL": "C0XXXXX9012" // #event
}
}
📋 チェックリスト: 新しいSlack連携を追加する時
新しい機能でSlack連携を実装する際は、以下のチェックリストを確認しましょう。
1. 通信の方向を確認
- Slackへ送信する? →
SLACK_BOT_TOKENが必要 - Slackから受信する? →
SLACK_SIGNING_SECRETが必要 - 外部から受信する? →
WEBHOOK_SECRETが必要
2. 認証情報の取得と設定
- 必要なトークン/シークレットを取得した
- Cloudflare Dashboardで環境変数を登録した
- "Encrypt" チェックを有効化した(トークン/シークレットの場合)
- wrangler.jsonc にチャンネルIDを追加した(必要な場合)
3. セキュリティ対策
- トークンをコードに直接書いていない
- GitHubのsecretとして登録した(CI/CD使用時)
- Slackからのリクエストは署名検証している
- ユーザー入力はサニタイズしている
4. テスト
- ローカルで動作確認した(
wrangler dev) - 本番環境で動作確認した
- エラーハンドリングを実装した
- Slack通知が正しいチャンネルに届いた
🎓 まとめ
Slack連携の認証情報は、通信の方向で考えると整理しやすくなります。
| 方向 | 認証情報 | 保存形式 |
|---|---|---|
| 🚀 送信 | SLACK_BOT_TOKEN |
Secret |
| 🔒 受信(Slack) | SLACK_SIGNING_SECRET |
Secret |
| 🌐 受信(外部) | WEBHOOK_SECRET |
Secret |
| 📍 送信先 | SLACK_*_CHANNEL |
平文 |
覚えておくべき3つのポイント:
- トークンは送信用、シークレットは検証用
- Workerは署名検証が必要、Pagesは不要(直接受信しないため)
- Bot の役割を明確にすると、トークンの使い分けが自然に決まる
この整理により、「あれ、どのトークンを使うんだっけ?」という迷いがなくなり、自信を持ってSlack連携を実装できるようになります。
関連記事:
更新履歴:
- 2026-02-21: 初版作成