Slack OAuth token rotationでrefresh失敗を防ぐ|新旧tokenの安全な切替

Slackのtoken rotationでは、refresh tokenは再利用できる固定資格情報ではありません。

AIものさし編集部

Slackのtoken rotationでは、refresh tokenは再利用できる固定資格情報ではありません。Slack公式仕様ではrefresh tokenを使って新しいaccess tokenとrefresh tokenを取得し、利用済みrefresh tokenは失効します。更新結果から新しい組を保存しないまま古いrefresh tokenを並行利用すると、次の更新で失敗し、Slack APIへのアクセスが止まる原因になります。ここでは、installationごとのtoken pairを安全に更新する設計と、refresh失敗時の切り分けを説明します。

最初に確認するのは、アプリでtoken rotationが有効かどうか、どのinstallationのどのtoken typeを扱っているかです。Slackのtoken rotationはOAuth granular permissionsを使うSlack app向けの仕組みとして案内されています。rotation有効化後のaccess tokenは12時間で期限切れとなり、refresh tokenを用いて更新します。アプリ設定を有効にする前に、開発環境で期限切れ・更新・保存・再起動まで試してください。Slackのドキュメントは、一度有効にするとoffにできないと注意しています。既存システムへ後付けする場合は、実装の準備ができてから切り替える判断が必要です。

【tokenの関係を把握する】

rotationではaccess tokenだけでなくrefresh tokenも状態として保存します。refresh成功応答には新しいaccess token、expires_in、refresh_token、token_type等が含まれます。古いrefresh tokenを新しいものへ置き換えることが必須です。token pairのうち片方だけを更新すると、access tokenは使えるのに次回refreshができない、または有効なrefresh tokenを失うという不整合が起こります。installation ID、token type、access token、refresh token、expires_at、scopeなど、運用上必要な情報を明確な単位で管理します。secret値を通常のログや分析イベントに出さないことも前提です。

表は横にスクロールできます

保管項目目的注意点
installation keyどのworkspace/app installationかを特定workspace名など可変の表示名だけを鍵にしない
token typebot、user、app-level等を識別型ごとの利用範囲を混同しない
access tokenSlack API呼び出しに利用expires_atと併せて扱う
refresh token次のaccess tokenを取得一度使ったtokenを再利用しない
expiration更新期限の判断expires_inから絶対時刻を計算し時計差を考慮
credential version保存世代を判別stale workerによる古い値の上書きを検出

これは実装例であり、Slackが定めた必須DB schemaではありません。既存のtoken storeがある場合は、同等の対応関係を作れるか確認してください。

【並行refreshを防ぐ】

失敗の典型は、期限到来時に複数のAPI requestやworkerが同じinstallationのaccess tokenを検知し、同じrefresh tokenで同時にoauth.v2.accessを呼ぶことです。refresh tokenは使い捨てなので、一方の応答が新token pairを返したあと、もう一方が古いrefresh tokenを使うと失敗し得ます。さらに、遅れて返ったworkerが古いtoken pairをDBへ書き戻すと、最新の組が失われます。

installation単位のsingle-flightまたは分散lockを使い、refresh中は他のcallerに同じ更新をさせない設計にします。ロック取得後にDBを再読込し、すでに別workerがaccess tokenを更新していないか確認します。更新済みなら新しいaccess tokenを使い、再refreshしません。ロックは期限付きとし、プロセス停止後に永久に残らないようにします。複数リージョンや複数プロセスでは、プロセス内mutexだけでは排他にならないため、共有DB、分散lock、条件付き更新などの仕組みを検討します。

更新処理は次の順にすると競合を減らせます。

  1. installationのtoken recordを読み、期限とversionを確認する。

  2. installation単位でrefresh lockまたはleaseを取得する。

  3. lock取得後にrecordを再読込し、他workerの更新がないか確認する。

  4. 現行refresh tokenでoauth.v2.accessへ一度だけ要求する。

  5. 成功時はaccess token・refresh token・expires_atをひとまとまりとして保存する。

  6. 保存時に読み取ったversionとの一致を条件にし、古いworkerの上書きを拒否する。

  7. 永続化を確認してからlockを解放し、待機callerへ更新結果を共有する。

API呼び出しが成功しても、その前にプロセスが停止して新しいpairを保存できなければ回復が難しくなります。保存処理は暗号化・アクセス制御されたデータストアで行い、保存失敗を「更新成功」と扱いません。新しいrefresh tokenは利用済みtokenを置き換えるため、後からログやメモリの古い値を再保存する経路がないか、token versionを使って検出してください。

【失敗の切り分け】

oauth.v2.accessの失敗をすべて「tokenが切れた」とまとめないでください。requestのclient credentials、grant_type、refresh token、content type、token type、ネットワーク応答、Slackの返却エラーを安全に確認します。Slack公式のtoken rotation guideはclient_id、client_secret、grant_type=refresh_token、refresh_tokenを送る更新例を示しています。API method referenceでは受け付ける形式やOAuth methodの情報が定義されるため、実際の実装と照合します。秘密値はログへ記録せず、エラー分類と相関IDだけを残します。

再試行は、refresh tokenがすでに消費された可能性を考慮して行います。timeoutが発生したとき、Slack側でrefreshが成功し新token pairを返したものの応答を受け取れなかった可能性があります。この状態で古いrefresh tokenを何度も呼び出すと失敗を重ねる場合があります。単純な無限retryを避け、保存済みtoken version、直近の更新ログ、Slack API応答を照合します。どちらが最新か確定できず、有効なpairが残っていない場合は、installationの再認証が必要か判断します。原因不明のまま全installationのtokenを削除するのは避け、対象installationに限定します。

エラーが発生したときは次の順に確認します。

  1. 対象installationとtoken typeが正しいか。

  2. rotationを有効にした時期と、旧long-lived tokenをexchange済みか。

  3. 保存したexpires_atと実際のAPIエラーにずれがないか。

  4. client credentials、grant_type、送信形式が正しいか。

  5. 同一installationに同時refreshが走っていないか。

  6. 成功応答の新しいrefresh tokenが完全に保存されたか。

  7. 遅いworkerや再試行処理が古いpairを書き戻していないか。

  8. 最新pairを特定できない場合、対象installationの再認証が必要か。

【障害時の復旧と再認証】

Slack公式Docsでは、refresh tokenは一度だけ使用でき、交換後に以前のtokenは短い猶予期間の後に失効すると説明されています。最初の長期tokenをrotation用にexchangeする操作も、同じaccess tokenを何度もexchangeするものではありません。すでに一度exchangeした長期access tokenを再exchangeしようとしたり、元のrefresh tokenを繰り返し使用したりしないでください。

有効な新token pairが保存されているなら、そのpairで更新を再開し、token storeのversionと更新時刻を確認します。pairが不明・破損しているなら、同じ古いrefresh tokenを自動再試行し続けず、再認証・再インストールが必要かをinstallation単位で判断します。Slack docsには、token exchange後に旧long-lived access tokenが失効することや、再exchange・revokeの扱いに注意事項があります。復旧手順を運用Runbookに書く前に、現在の公式手順とアプリの配布形態を確認してください。

【導入前テスト】

開発workspaceで次を検証します。

□ token rotationが有効なinstallationを識別できる

□ expires_inから期限を記録し、期限前に更新を開始する

□ 同じinstallationへの同時refreshが1回に集約される

□ 成功応答のaccess/refresh pairを一括保存できる

□ DB保存失敗時に成功状態へ進まない

□ stale workerが古いpairを書き戻せない

□ refresh timeout時に無条件の無限retryをしない

□ credentialやresponse bodyのtokenをログへ出さない

□ process restart後に最新pairでSlack APIを呼び出せる

□ token typeやinstallationを取り違えない

□ 期限切れ・無効tokenのエラー時に対象installationを特定できる

□ 再認証の要否と担当者が明確である

このチェックリストは検証計画であり、本稿作成時に実際のSlack Appでtoken refreshを試した結果ではありません。テストは本番利用者や本番installationではなく、分離したdevelopment workspaceで行い、実際に更新後のpairが永続化されるところまで確認します。

【運用設計の要点】

Slackの公式仕様で決まるのは、refresh手順やtokenの一回利用、期限などの製品挙動です。single-flight、lock、version付き保存、stale worker防止、アラートや再認証Runbookは、それらの制約を踏まえたアプリ側の設計です。仕様と設計提案を混同せず、どの保証がSlackから提供され、どの障害対策を自分たちで実装するのか分けて記録します。

監視では、refresh成功率、期限切れ前の更新率、oauth.v2.accessのエラー分類、lock競合、token store書き込み失敗、stale write拒否、再認証が必要なinstallation数を見ます。token本体を記録せず、installation識別子も必要最小限にします。広範な失敗が起きたら、まず新規refreshの競合を止め、保存済みtoken pairの世代を確認します。最も新しい成功応答からpairを復元し、対象installationを限定して再認証を検討します。

【結論】

安全なtoken rotationの鍵は、refresh tokenを一度だけ使うものとして扱い、installationごとに更新を直列化し、応答で返るaccess/refresh pairを原子的に保存することです。並行request、timeout、DB書き込み失敗、遅れて戻るworkerをテストし、古いpairが復活しないことを確認します。更新状態が曖昧なときに古いrefresh tokenを繰り返し投げるのではなく、保存履歴から最新pairを特定し、できなければ該当installationの再認証へ進む設計にしてください。

Sources

Editorial Note

本文ではSlackのtoken rotation仕様と、競合・障害に備えるアプリ側の設計推奨を分けています。refresh実行や実環境の動作確認は行っていません。