Azure Botで発生するSlack API errorの原因を探る【Token Rotation】

Azure Botでアプリを実装してSlackに連携させようとしたとき、こんなエラーが出ました。

「Slack API errorはいくら何でも幅が広すぎないか、?」という感じですが、ドンピシャでこのエラーの対処法をまとめている記事がなかったので、今回はこのエラーに対処するためにやったこと、解決した方法について、備忘録的に解説していきます。

原因特定のためにしたこと

1. 関連付けIDからエラーログを調べる

より詳細なエラーログを求め、Azureの関連付けIDを使用してログを検索してみることにしました。 まずはAzureのCloud Shell。

az monitor activity-log list --correlation-id <関連付けID>

見ての通り、中身は空でした。 クエリを使用して関連付けIDに関連するログを調べてみましたが、こちらも同様に何の手がかりも得られませんでした。

AzureActivity
| where CorrelationId == "<関連付けID>"

2. Slack APIの設定に問題がないか調べる

「Slack API error」ということなので、Slack APIの設定に問題がある可能性が高いと考え、見直しをしました。 調べる箇所としては、

  • App Credentialsの値がAzureに正しく入力されているか
  • OAuthの設定は正しいか
  • Scopeの設定は適切か

あたりだと思います。ここでも問題は見つかりませんでしたが、設定を巡回しているうちに原因はもう一歩上の段階に存在していることが分かりました。それが、Configuration Tokenです。

Configuration Tokenは、SlackのWorkspaceごとに発行されるトークンであり、こちらの期限が12時間で切れてしまうことが原因となり、Slack API errorを引き起こしていたようです。ボットアプリ単体の設定ではなく、Workspaceの設定が問題だったようで、こちらを定期的に自動更新してくれる仕組みを作れば解決しそうです。

解決法

12時間たってConfiguration Tokenが期限切れとなる前に、自動でトークンを更新してくれる仕組みを作ります。 Boltと呼ばれるSlackが作成したツールを用いると、トークンの更新も含め楽に実装できるようですが、せっかくなので今回はAzureの関数アプリ(Functions)を用いて定期的に自動更新してくれるアプリとして作成することにしました。

Boltに関して詳しく知りたい方はこちらを参照してください。Tools built by Slack

開発に用いた環境は下記の通りです。

  • Azure 関数アプリ
  • VS Code (+Azure Functionsの拡張機能)
  • Node.js – 20

関数アプリのTimer Triggerという機能を用いてNode.jsでコードを書いていきます。VS Codeでプロジェクトを作成し、下記のコードを入力します。(簡単のために生身のトークンをコードに書いていますが、セキュリティ的には秘匿にすることが望ましいです。)

const { app } = require('@azure/functions');

let refreshToken = "<初期のRefreshTokenを入力>";

app.timer('rotating', {
    schedule: '0 0 */10 * * *',
    handler: async (myTimer, context) => {
        context.log('Timer function processed request.');

        // Dynamic import of 'node-fetch'
        const fetch = (await import('node-fetch')).default;

        async function callApi() {
            try {
                const res = await fetch(`https://slack.com/api/tooling.tokens.rotate?refresh_token=${refreshToken}`);
                if (!res.ok) {
                    throw new Error(`HTTP error! status: ${res.status}`);
                }
                const content = await res.json();
                refreshToken = content.refresh_token;
                context.log(refreshToken);
            } catch (error) {
                context.log(`Error in callApi: ${error.message}`);
            }
        }

        await callApi();
    }
});

設定としては10時間に1回更新するようにしています。

コードで使用しているSlackのAPI(tooling.tokens.rotate)についてはこちらの公式リファレンスにわかりやすくまとまっています。

tooling.tokens.rotate

途中で「node-fetch」を動的インポートしているので、プロジェクトのディレクトリに下記のコマンドからnode-fetchをインストールしておく必要があります。

npm install node-fetch

この際、自動的にpackage.jsonのdependenciesが書き換わらなかったので、手動でnode-fetchの記述を追加しておくことを忘れずに行なってください。

{
  "name": "rotating",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "func start",
    "test": "echo \"No tests yet...\""
  },
  "dependencies": {
    "@azure/functions": "^4.0.0",
    "node-fetch": "^2.7.0" // この記述を追加しておく!
  },
  "main": "src/functions/*.js"
}

以上、完了したらAzureの関数アプリにデプロイします。 少し時間が経つのを待って、Slack API上でConfiguration Tokenが自動更新されていること確認してください。Azure上のSlack API errorも発生しなくなっていれば、無事解決です。