LoginSignup
25
19

More than 3 years have passed since last update.

Slack Appに挑戦(4) - OAuthとトークン

Last updated at Posted at 2019-10-14

Slack Botを真面目に学ぶ
今回はSlackのOAuthをやってみます。

はじめに

SlackのWebAPIを使うときにはAPI用のTokenを求められます。Tokenを取るためにSlackではOAuthという仕組みを提供していますが、いまいち理解できていませんでした。ということで、SlackのOAuthをちゃんとやってみようと思います。
https://api.slack.com/docs/oauth に英語で書いてあることをPHP Laravel + Herokuでやっているだけです。

環境

  • Heroku
    • PHP 7.3
    • Laravel 6.0.3

OAuthとは

OAuthについては、詳しい資料がたくさんあるので省略します。

簡単に言ってしまうと、
Slackワークスペースに登録したSlack Appが、ワークスペースユーザのIDとパスワードを預からずに、ユーザの代理でSlackを操作する ために、ユーザとSlackから許可を受ける仕組み、といいましょうか。
ユーザから正しく許可を得た結果、App作者はSlackからトークンを受け取ることになります。このトークンを使ってユーザの代わりにSlackを操作するわけです。

勝手に誤解ポイント1

レガシートークンではない最近のトークンは、SlackAppを作成してApp経由でトークンを得る流れになっています。Slack Appを作ってみるとわかりますが、AppのOAuth&Permissionのページに行くと、特に何もしていないのにトークンが表示されています。
image.png

上がUser Tokenで、下がBot Tokenと区別されていますが、どちらもAPIを操作するのに使います(実際に使えます)。自分用のユーザートークンを手にしている状態なので、OAuthする必要性が分かりにくくなってるような気がします。トークンを取る仕組みがOAuth、でも、もうトークン持ってるけど、、、あれ?って感じです。

勝手に誤解ポイント2

ユーザがOAhthをリクエストをするときに、利用可能な権限を絞ります。Slack Appの管理画面にもScopeを設定する画面があり、自分が作るフォームにも権限を設定するパラメータがあり、違いが分からないと混乱します。

SlackのScope設定画面
image.png

この画面が、Appの設定画面の中にあるので、

  • Slack Appが必要とする権限をAppのコントロールパネルで設定をし、
  • そのScopeでOAuthの認可が行われて、
  • Appが必要なScopeが発行したトークンに設定される

と思っていましたが、違いました。

この画面で設定するのは、開発者自身のユーザトークンに対するScopeの設定だけです。他のユーザのトークンは全く関係がないことに気が付くのに、3日ほど掛かりました。

:sunny:2つの誤解点を簡単に言えば、
開発者自身のトークン発行と権限設定は、Slackが用意したGUIでやらせてあげるけど、ワークスペース内のユーザから権限をもらうときには手続きを踏んでちゃんとやるんだぞ、ということです。

OAuthのトークン取得を実装

App開発者の場合、Appのコントロールパネルにアクセスできてしまうため、自分のトークンは簡単に知ることができます。これだとOAuthのありがたさもトークンの大切さも分かりません。Slackが推奨するOAuthのプログラムを作ってみます。

作るものは、

  1. Slackに認可リクエストを送るためのFormを作る
  2. ユーザがユーザの意思でFormを送信
  3. Formの内容がSlackに届く
  4. SlackからLaravelにコールバックが届く
  5. コールバックに含まれる値を読みほどいて、Slack宛てにトークン発行のリクエストを送る
  6. レスポンスとしてトークンが届く
  7. トークンを表示

フォームの実装

認可を実行するときに表示するフォームです。全てがHidden要素なので、ユーザがやることはありません。ボタンを押してもらうだけです。client_idはApp固有の値, teamはワークスペース固有の値ですが、フォームに露出するのは問題ないです。

resources/oauth/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Slalack Bot OAuth</title>
    </head>
    <body>
        <div class="flex-center position-ref full-height">

            <div class="content">
                <div class="title m-b-md">
                    slalack-bot OAuth
                </div>
                <form action="https://slack.com/oauth/authorize" method="GET">
                    <input type="hidden" name="scope" value="identity.basic">
                    <input type="hidden" name="client_id" value="{{config('slack.slalack.client_id')}}">
                    <input type="hidden" name="redirect_uri" value="{{config('slack.slalack.redirect_url')}}">

                    <input type="hidden" name="state" value="slalalala">
                    <input type="hidden" name="team" value="{{config('slack.team')}}">
                    <input type="submit" value="認証ページへ">
                </form>
            </div>
        </div>
    </body>
</html>

scopeパラメータは、コントロールパネルで設定したScopeと関係がありません。このフォームをPOSTするユーザが何を許可するか(トークンを知る人にどれだけの自由度を与えるのか)を、全部並べておく必要があります。

Scopeのリストはこちらにあります。複数のScopeを同時に指定する場合には、スペースで区切ります。
https://api.slack.com/docs/oauth-scopes

コントローラーの実装

いつも通り、artisanコマンドでOAuth用のコントローラーを作り、

command
 php artisan make:controller OAuthController

コントローラーの実装をします。
初回表示時にSlackに送り込むフォームを表示するだけのindexメソッドと、Slackからコールバックされるauthメソッドの2つを実装しています。

OAuthController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class OAuthController extends Controller
{
    const SLACK_OAUTH_URL = 'https://slack.com/api/oauth.access';

    //フォームを表示するだけ
    public function index(Request $request ){
        return view('oauth.index');
    }

    //Slackのredirect_urlからコールバックされるメソッド
    public function auth(Request $request){
        \Log::info($request);
        $code = $request->input('code');
        $state = $request->input('state');

        if($state != 'slalalala'){
            return response('Something Wrong!', 500);
        }
        if($request->filled('error')){
            return response('slack returned error.',500);
        }

        $guzzile = new \GuzzleHttp\Client();

        $params = [];
        $params['code'] = $code;
        $params['client_id'] = config('slack.slalack.client_id');
        $params['client_secret'] = config('slack.slalack.client_secret');

        $option = [];
        $option['form_params']=$params;

        $response = $guzzile->post(self::SLACK_OAUTH_URL, $option);
        $body = $response->getBody();
        \Log::info($body);

        $data = json_decode( (String)$body, true);
        if(!$data['ok']){
            return response('OAuth request returns error!', 500);
        }
        $token = $data['access_token'];
        $userName = $data['user']['name'];
        $userId = $data['user']['id'];
        $teamId = $data['team']['id'];
        \Log::info([$token, $userName, $userId, $teamId]);

        var_dump((String)$body);

        //この先でトークン情報をDBなどに保存しておくこと
        //失うともう一度OAuthする必要がでてくる

        //最後にLaravelのルールでViewの情報を返す。(今回はサンプルなのでOKだけ戻す)
        return "ok";
    }
}

authメソッドはSlackから呼び出されます。渡されたcodeの値とclient_secret, client_idを使って、/oauth.accessにリクエストを送り、トークンの発行しています。stateの一致は適当な実装ですが、optionなので、やらなくてもOKです。細かい実装手順とパラメータはこちらに書いてあります。
https://api.slack.com/docs/oauth#step_3_-_exchanging_a_verification_code_for_an_access_token

routesの設定

今回はHTMLでのウェブアプリケーションになるので、Laravelのweb.phpのルートに2つのコントローラーのメソッドを登録します。

routes/web.php
Route::get('/oooindex', 'OAuthController@index');
Route::get('/oooauth', 'OAuthController@auth');

https://slalack-bot.herokuapp.com/oooindex が入力用ページ、
https://slalack-bot.herokuapp.com/oooauth がSlackからコールバックされるURLです。

configの設定

configディレクトリ以下にslack用のコンフィグを作りました。Laravel的には、config('slack.slalack.client_id')のパスで参照します。

client_idはAppで固有な値で変わることがないので、設定ファイルに直接書きました。そのままGITにコミットしても問題ない値です。漏れると問題のある値client_secretは.envファイルから取るようにしておきます。
設定ファイルにsigning_secretも記載がありますが、前回の名残でOAuthには関係ありません。

config/slack.php
<?php
return [
    'team'=>'TDWRKFV0S',
    'slalack' =>[
        'signing_secret'=>env('SLACK_SIGNING_SECRET'),
        'client_secret'=>env('SLACK_CLIENT_SECRET'),
        'client_id'=>'472869539026.770655600150',
        'redirect_url'=>'https://slalack-bot.herokuapp.com/oooauth',
    ],
];

.env

2行追加します

.env
SLACK_SIGNING_SECRET=xxx000
SLACK_CLIENT_SECRET=xxx111

Herokuの場合は.envを使わないので、config:setするのを忘れなく。

command
heroku config:set SLACK_CLIENT_SECRET=xxx111

Herokuにデプロイしましょう。これでプログラム側は準備完了です。

Slack Appの設定

OAuth & Permissionメニューの中段くらいにあるRedirectURLにコールバックのURLを設定します。
image.png

Formから送ったredirect_uriとAppに設定したRedirect URLのHostとPortが一致しているか確認されます。無難に完全一致させておくのがいいと思います。

これで準備完了です。

実行

トークン発行を実行

認可ページを表示

Slackにログインしていると動作がわかりにくくなるので、シークレットモードかプライベートウィンドウを開き、https://slalack-bot.herokuapp.com/oooindex ページを開きます。ボタン1つの画面が出ます。
image.png

ワークスペースを決める

ボタンを押すと、https://slack.com/oauth/authorize にリクエストが飛びます。ログイン前の場合、対象となるワークスペースを聞かれます。Slackが出している画面です。
image.png

ログインユーザを決める

そのあと、ワークスペースのユーザ情報を聞いてきます。これもSlackが出している画面です。ログイン情報を直接、アプリケーション開発者が受け取るわけではありません。
image.png

Slack Appとアカウントの連携を承認する

ログインが完了するとこの画面が出ます。これは、連携しようとしているSlack App(slalackという名前のApp)が、ログインしたユーザの代わりに何をやらかすのか、説明するための画面です。これもSlack側で出しているものです。
image.png

トークン発行

「許可する」ボタンを押すと、OAuthControllerのauthメソッドがSlackから呼び出されます。codeを取り出して、client_idclient_secretでトークンの発行を行います。今回のプログラムはトークンを含めて受け取った値を表示しているだけです。
image.png

:white_check_mark: 開発者自身のSlackアカウントでトークンの発行フローをやってみるとわかりますが、OAuth & Permissionで表示されるトークンと同じものが手に入ります。つまり、開発者に限っては、トークン発行のフローを経由せずに(いちいちシステムを作らずに)トークンを入手できるってことです。


ワークスペースにいる別の人(Alice)のアカウントでやってみると、別のトークンが発行されます。あたりまえですね。
image.png

:warning: 今回は実装していませんが、トークンを発行を受けたあとに、ユーザIDとトークンをDBなどの保存しておく必要があります。

発行済みトークンの確認

自分がどのアプリケーションにどの権限を渡しているのかを確認できます。
https://api.slack.com/tokens
image.png

編集ボタンを押すと、Appの詳細が確認できます。
image.png

下のほうにAppで許可した権限が一覧で出てきます。トークンの値を確認することはできません。
image.png

まとめ

ワークスペース内のユーザがBotに話しかけても、/コマンドを実行しても、Botか開発者のアカウントが反応するアプリケーションの場合は、各ユーザのトークンを発行する必要はありません。Botに話かけたら、話しかけたユーザとして発言したりするときには必要になります。会社のワークスペースで遊ぶくらいの場合は必要ないかもですね。

現状のSlackのトークンには有効期限がありません。リフレッシュトークンによるトークン再発行の仕組みは現在開発中とのことです。(2019年10月14日現在、使うことはできませんでした)
https://api.slack.com/docs/rotating-and-refreshing-credentials

参考資料

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be
https://qiita.com/subarunari/items/3e4c6060fcefd4c65257
https://qiita.com/dbgso/items/a95a3364d9a8c67f3387

25
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
19