nado

Slackアプリで作成者以外のユーザートークンを発行する

2022.04.07

先日Slackアプリを開発していて、作成者である自分以外のユーザーでもアプリを介してメッセージを投稿できるようにする必要があったのですが、欲しい情報になかなか辿り着けず小一時間ほど溶かしました。
「slack oauth token」や「slack oauth 認証」などで調べれば欲しい情報が出てくるのですが、「slack ユーザートークン 発行」などのキーワードではSlackアプリ作成方法のような記事しか出ず、公式のドキュメントも少し煩雑で探しづらいと感じたのでこのようなタイトルで記事を残しておきます。

トークン発行までの流れ

トークン発行までのざっくりとした流れは以下のようになります。

  1. Slackアプリからワークスペースのユーザーにアクセスするためのリクエスト(認可リクエスト)を送る

  2. ユーザーからリクエストが承認されると指定したURL(後述)にGETパラメータ付きでリダイレクトされる

  3. 受け取ったGETパラメータに含まれている認可コードを利用してSlackのAPIにPOSTするとトークンが発行されレスポンスとして返却される

Slackアプリ側で必要な設定

Slackアプリから認可リクエストを送るために必要な設定をします。
※Slackアプリの作成方法については調べればすぐ出てくるので省略します

Redirect URLs

こちらから作成したアプリのページを開き、左側のメニューにあるOAuth & Permissionsを選択します。
遷移した画面の中ほどにRedirect URLsという項目があります。

Redirect URLs

トークン発行までの流れの2で少し触れたように、認可リクエストが承認されるとリダイレクトが発生するのでリダイレクト先のURLをここで設定します。
localhostなど、ローカルで立ち上げたサーバーのURLを設定している記事も見かけましたが、現在はSSL化されたURLでなければ設定できないようになっていたので予め用意する必要があります。
私はCloud functionsを利用しましたがHerokuCloudflare Workersなどアプリケーションが実行できる環境であれば何でも良いです。
後ほどここで用意したサーバーにSlackのAPIへPOSTする処理をデプロイしていきます。

Scopes

次にRedirect URLsの下にあるScopesの設定をします。

Scopes

ScopesではSlackアプリに対してSlack上で実行できる操作の権限を与えます。
Add an OAuth Scopeをクリックすると権限の一覧が表示されるので必要に応じて選択してください。
メッセージを投稿するだけであればchat:writeのみで大丈夫です。

認可リクエストを送る

ここまででアプリ側の設定はできたのでSlackワークスペースとユーザーに対して認可リクエストを送ります。
認可リクエストを送るためには以下のURLに必要なパラメータを付与してアクセスします。

ParamsDescription
user_scopeScopesで設定した権限
client_idSlackアプリを作成した時に発行されるID。Basic Informationから確認できる
redirect_uriRedirect URLsで設定したURL

アクセスすると「【アプリ名】 が 【ワークスペース名】ワークスペースにアクセスする権限をリクエストしています」という画面が表示されます。
ページ下部の「許可する」を押下すると認可リクエストが送信されRedirect URLsで設定したURLにcodeというパラメータ付きでリダイレクトします。

認可コードを元にトークンを発行する

最後にGETパラメータで受け取った認可コードを用いてSlackのAPIにPOSTします。
使用するAPIはこちらです。
必要なパラメータを付与してPOSTすることでトークンが含まれたレスポンスが返却されます。
サンプルとして以下にNode.js(Nest.js)のコードを記載しておきます。
client_idclient_secretの部分は自身の環境に応じて変更してください。

// app.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { Request } from 'express';
import type { OauthV2AccessResponse } from '@slack/web-api';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  async getToken(
    @Req() request: Request,
  ): Promise<OauthV2AccessResponse | string> {
    const code = request.query.code as string | undefined;
    const error = request.query.error as string | undefined;
    const clientId = process.env.client_id; // 認可リクエストを送る際にも使用したClient ID
    const clientSecret = process.env.client_secret; // Client IDの下で確認できるClient Secret

    return this.appService.getToken(code, error, clientId, clientSecret);
  }
}
// app.service.ts
import { Injectable } from '@nestjs/common';
import { WebClient } from '@slack/web-api';
import type { OauthV2AccessResponse } from '@slack/web-api';

@Injectable()
export class AppService {
  async getToken(
    code: string | undefined,
    error: string | undefined,
    clientId: string,
    clientSecret: string,
  ): Promise<OauthV2AccessResponse | string> {
    if (code) {
      const slack = new WebClient();
      const result = await slack.oauth.v2.access({
        client_id: clientId,
        client_secret: clientSecret,
        code,
      });
      return `access_token: ${result.authed_user.access_token}`;
    } else if (error) {
      return '認証がキャンセルされました。';
    }
  }
}

まとめ

OAuthについてある程度理解していて、SlackアプリがOAuth認証を利用していることが分かっている人にとっては難しい話ではないのですが、そうでない人にとっては欲しい情報に辿り着くまでに多少労力を費やしそうだなと感じました(私もそのうちの一人でした)。
この記事によって誰かの労力を少しでも減らすことができれば幸いです。