11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Slack Appに挑戦(2) - Request Verification

Last updated at Posted at 2019-09-29

SlackaAppをまじめに学ぶ(2回目)

#はじめに
前回、Heroku上にウェブアプリケーションを構築し、Slackワークスペースからスラッシュコマンドを受け取るところまでやりました。インターネット上にSlackからのリクエストを受け付ける先を公開したわけですが、送信元がSlackに限られるわけではなく、悪意があれば誰でもリクエストを送ることができるので、怖いですね。
ということで、公開したAPIへのリクエストが、自分が作ったSlack Appから送られたものかを検証する方法を実装しておきます。

ここに英語で書いてあることを、PHPとLaravelでやっているだけです。
https://api.slack.com/docs/verifying-requests-from-slack

##今回使う環境

  • Heroku
    • PHP 7.3
    • Laravel 6.0.3
    • Clear DB
  • Windows
    • PHP 7.2.11
    • Laravel 6.0.3
    • Maria DB Ver 15.1

前回どおり、基本的にはWindowsでの作業です。

#やってみよう
検証方法は2つあります。1つ目は推奨されていないのでさらっと行きます

##1. Verification Token

この方法は推奨されていません。
何もしないより良いかなと思いますが、in comming monthで完全になくなると言っているので危ないかもしれません。

Verification token deprecation
We'll continue allowing apps to use verification tokens for now. However, we will retire them completely in coming months. We strongly recommend switching to request signing as soon as possible.

Basic InformationにあるVerification Tokenの値をメモします。
image.png

スラッシュコマンドのパラメータの一つとして、Verification Tokenの値がそのまま渡されます。
image.png

自分で作ったアプリケーションは、Basic Informationに表示されているTokenの値と、リクエストで渡ってきたTokenの値が一致するかを調べるだけです。一致するときは、正しいSlackのサーバーからきたと見なすことになります。Verification Tokenの値を秘密にしておく必要があります。

##2. Signing Secret

こちらが新しく推奨されているSigning Secretという方法です。

検証手順は以下のとおりです。

  1. Signing SecretをBasic Informationから調べる
  2. リクエストヘッダーのX-Slack-Request-Timestampを取得する
  3. リクエストボディを無加工で取得する
  4. Slackが決めたルールで文字列を結合する
  5. Signing Secretと4で作った文字列から、sha256のハッシュ値をとる
  6. リクエストヘッダーのX-Slack-Signatureで渡ってきた値と比較する
  7. 一致するときは正しいリクエストとみなす

これをLaravelで実装していきます。

###Controller
まずは、新しいContoroller(VerificationController.php)を作り、verifyメソッドとして実装します。

app/VerificationController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Log;

class VerificationController extends Controller
{
    public function verify(Request $request){
        $timestamp = $request->header('x-slack-request-timestamp');
        $signature = $request->header('x-slack-signature');
        $requestBody = $request->getContent();
        $secret = config('app.slack.signing_secret');

        $sigBasestring = 'v0:' . $timestamp . ':' . $requestBody;
        $hash = 'v0=' . hash_hmac('sha256', $sigBasestring, $secret);

        $debug = ['Timestamp'=>$timestamp,
            'Body'=>$requestBody,
            'Sig'=>$sigBasestring,
            'Secret'=>substr_replace($secret,'*',5),
            'Hash'=>$hash,
            'Signature'=>$signature,];


        return response()->json($debug);
    }

}

特に解説は不要ですね。
取得した値と計算した結果をreturnするだけのものです。

###route設定

作ったメソッドを/api/verify-testでアクセスできるように登録します。エンドポイントは、https://slalack-bot.herokuapp.com/api/verify-test になります。

routes/api.php
Route::any('/verify-test', 'VerificationController@verify');

###config/.env設定

.envにSigning Secretの値を登録する。
HerokuのConfigにセットするのを忘れずに。

.env
SLACK_SIGNING_SECRET=70cad**********

Laravelのconfig経由で、Signing Secretが取れるように登録します。

config/app.php
    /*
    |--------------------------------------------------------------------------
    | Class Aliases
    |--------------------------------------------------------------------------
    |
    | This array of class aliases will be registered when this application
    | is started. However, feel free to register as many as you wish as
    | the aliases are "lazy" loaded so they don't hinder performance.
    |
    */

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        //省略
    ],

    //ここ追加
    'slack' => ['signing_secret'=>env('SLACK_SIGNING_SECRET')],

];

Herokuにデプロイすると、以下のURLでアクセスできるようなります。
https://slalack-bot.herokuapp.com/api/verify-test

###スラッシュコマンド登録
Slackから/verify-testで呼び出すコマンドを登録します。RequestURLは https://slalack-bot.herokuapp.com/api/verify-test に設定します。

image.png

###動作確認

slackのメッセージボックスから/verify-testを呼びます。追加のメッセージは何でも構いません。
image.png

呼びかけに応じて、リプライが来ます。
image.png

だらだらとJSONがかえってきていますが、大事なのは最後の2つだけです。Hashが自分で計算して作った値、SignatureがSlackから送られてきた値です。両者が一致しているので実装方法に誤りはなさそうです。
一致しているので、このリクエストはSlackから送られてきた正規なリクエストとみなして良いということです。

"Hash":"v0=098ea4338850dc6004b89d24f2a2d06ac12f6f138121df9ad0a74bec8db32521"
"Signature":"v0=098ea4338850dc6004b89d24f2a2d06ac12f6f138121df9ad0a74bec8db32521"

#LaravelのMiddleware化する
各コントローラのメソッドでverifyメソッドと同じことをするのは面倒なので、Laravelのmiddlewareに実装して自動適用するように変えてみます。

##Middlewareクラスの生成
artisanコマンドでひな型を作ります。

php artisan make:middleware SlackRequestVerification
Middleware created successfully.

###検証ロジックを実装
作られたひな形のhandleメソッドに検証ロジックを実装します。VerificationControllerに書いた処理をそのまま持ってくるだけです。

App\Http\Middleware\SlackRequestVerification
<?php

namespace App\Http\Middleware;

use Closure;
use Log;

class SlackRequestVerification
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $timestamp = $request->header('x-slack-request-timestamp');
        $signature = $request->header('x-slack-signature');
        $requestBody = $request->getContent();
        $secret = config('app.slack.signing_secret');

        $sigBasestring = 'v0:' . $timestamp . ':' . $requestBody;
        $hash = 'v0=' . hash_hmac('sha256', $sigBasestring, $secret);

        $debug = ['Timestamp'=>$timestamp,
            'Body'=>$requestBody,
            'Sig'=>$sigBasestring,
            'Secret'=>substr_replace($secret,'*',5),
            'Hash'=>$hash,
            'Signature'=>$signature,];
        
        \Log::debug($debug);

        //不一致なら500で終わり
        if( $hash !== $signature ){
            return response()->json([
                'ok'=>false,
                'error' => 'signature error',
            ], '500');
        }

        //念のため、verificationをした証拠を入れておく
        $verified = [
            'slack_signing_verification'=>true, 
            'slack_use_secret'=>substr_replace($secret,'*',5),
            'slack_verify_hash'=>$hash,
        ];
        $request->merge($verified);

        //次の処理へ
        return $next($request);
    }
}

###Middlewareの登録
HttpのKernelに登録します。今回はSlackからのリクエストはすべて/api/*で受けるので、apiのmiddlewareグループに登録しました。

app/Http/Kernel.php
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            //ここに追加
            \App\Http\Middleware\SlackRequestVerification::class,

            'throttle:60,1',
            'bindings',
        ],
    ];

ここまでの変更をHerokuにデプロイします。

##動作確認
###正常動作
前回作った/debugを呼び出ししてみます。
image.png

image.png

無事、middlewareを経由して処理されて、Verificationが正常に終わり、Controllerに処理が伝わりました。middlewareでrequestに追加した3つの値も確認できました。

###エラー
ブラウザでGETのリクエストを送ってみると、500 HTTPレスポンスとJSONエラーメッセージが返ってきます。
https://slalack-bot.herokuapp.com/api/debug

{"ok":false,"error":"signature error"}

前回公開した/api以下のエンドポイントは、Slack経由の正しいリクエスト以外は全て500エラーになります。

#まとめ
Slack Signing VerificationをLaravelアプリに実装しました。Slackのサーバーから正しく届いたメッセージであることを確認する方法を入れたので、安心感が増しました。

#参考資料
https://qiita.com/girlie_mac/items/21fedcc6ce07aa44a670

11
2
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
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?