Posted at

TwilioからのリクエストであるかチェックするのはFormValidation上で行うとスマートな感じがする

More than 1 year has passed since last update.


このページについて

Twilioからのwebhookが、本当にTwilioサーバーから来たのか検査するのは、

FormValidationで行うのがイイんじゃない?という話。

が、何でこれを書こうと思ったのか覚えていない。

下書き漁ったらあったので、とりあえず書いてみます。


環境


  • PHP 7.1

  • Laravel 5.5

  • Twilio SDK 5.16


Twilioサーバーから来たリクエストか検査する方法

本家:https://jp.twilio.com/docs/api/security


バリデーションの手順


  1. 対象の完全URL、プロトコルから始まり、全クエリーストリングを含むもの (https から始まり、 ? で指定するものを全て含む) を取得します。

  2. もしリクエストがPOSTの場合、全てのPOSTパラメータをUnix形式の大文字小文字を区別するソート方法で、アルファベット順に並び替えます。

  3. ソートしたPOSTパラメータに、URLの最後まで繰り返しパラメータ名と値(区切り文字を使わない)を当て込んでいきます。

  4. 最終URLの文字列に、ユーザの AuthToken を鍵として(大文字小文字を区別します)、HMAC-SHA1 形式で署名します。

  5. 結果のハッシュ値をBase64でエンコードしてください。

  6. 結果のハッシュ値とTwilio側が署名した X-Twilio-Signatureヘッダを比較します。 これらが一致すれば問題ありません。

だそうです。

めんどくさくてやる気にならない・・・

でも大丈夫、僕たちにはtwilio-sdkがある


Twilio-SDKを使った実装

\Twilio\Security\RequestValidatorクラスを使う


validation.php

public function twilioValidation()

{
$token = '12345';

$validator = new RequestValidator($token);
return $validator->validate($_SERVER['HTTP_X_TWILIO_SIGNATURE'], url()->current(), $_POST);
}


$tokenはTwilioのトークン情報で、本来はDBや設定ファイルに持ってる内容です。

ちなみにこのメソッド、GETでwebhook受けるようにしてると動かないので、

Twilioサーバーからのリクエストかを検査したい箇所は、全てPOSTで受ける必要があります。

RequestValidator::validateについて、

* 第1引数:固定

* 第2引数:webhookのurl 検査という意味だと固定値持たせた方が良いのかもしれないけど、受けたリクエストのURLを突っ込む

* 第3引数:POSTパラメータは全て渡す必要があるため、$_POSTで固定

実装自体はこれだけでOKなのですが、これをどこに置くのが良いか?

というのがこのページの主題です。


バリデーションの置き場


ミドルウェア

最初に思ったのはここ。

Twilioとやり取りするルーター定義に、ミドルウェアで設定して一律やったら楽じゃないかと。

設定はルーティングファイルに書けば良いから、バリデーション目的ならここでも構わなかったりする。

が、ここに書くとPOSTされたデータの吸い上げを、コントローラ側の各処理でやらないとイケないから、

それは案外イケてないのではないかと思って辞めました。


バリデーション(独自ルール)

じゃあ、ということでバリデーションに実装することにしました。

独自のルールとして作るかなぁ、と。

これはあっさり辞めました。

だって、特定のデータに依存しないから。


バリデーション(FormValidation::autorize)

で、辿り着いたのがここ。

Laravelの認可の標準的な考え方とはずれはするけど、

Twilioサーバーからのリクエストなら認可する、という考え方をするならここだな、と。

ここに置けば、コントローラに渡すRequestの属性値も同時に設定できるし、割とスマートかな、と。


TwilioRequest.php


use Illuminate\Foundation\Http\FormRequest;
use Twilio\Security\RequestValidator;

class TwilioRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
* @throws \Exception
*/

public function authorize()
{
$token = '12345';

$validator = new RequestValidator($token);
return $validator->validate($_SERVER['HTTP_X_TWILIO_SIGNATURE'], url()->current(), $_POST);
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/

public function rules()
{
return [
'CallSid' => 'required',
'Caller' => 'required',
'Digits' => '',
'From' => 'required',
'To' => 'required',
];
}
}


全容はこんな感じ。

rulesで返している値を見れば、コントローラ側でTwilioのどのパラメータを使っているか判るのがミソ。

使い方は、コントローラのメソッドの引数にこのクラスを書いとくだけ。

webhookで受けるメソッド全てに上記を設定しないといけないけど、

使用しているTwilioのパラメータが何か判ることの方がメリットが大きいと判断しました。


おわりに

Twilioとのやり取りではなくて、

バリデーションの方法について記載してみました。