はじめに
-
SendbirdのWebhookをCloudFunctionsで受け付けたい
-
リクエストがSendbirdからのリクエストであるか、または改ざんされていないかを確認するために署名を検証する必要がある
-
上記をやろうとした際にハマったこととその解決策を共有します
実装した内容
Cloud FunctionsにてSendbirdからのWebhookリクエストを受け付ける関数を実装する中で、データが改ざんなどされていないかを検証する必要がある
具体的には...
リクエストされた際にSendbirdサーバー側にてPOSTリクエスト本文とAPIトークンを利用して暗号化を行いハッシュ値を生成します
生成されたハッシュ値は x-sendbird-signature
に設定されて送られてきます
それを、Cloud Functions側で同じAPIトークンとPOSTリクエスト本文でハッシュ値を生成して内容が同じかどうかを比較し改ざんを検証します
Sendbird公式で紹介されている実装方法
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = 5001;
const API_TOKEN = 'SENDBIRD_MASTER_API_TOKEN';
// The body should be a string.
// Validation fails if the body is passed as a JSON object and then stringified.
app.post('/sendbird-xsig', express.text({ type: 'json' }), (req, res) => {
const body = req.body;
const signature = req.get('x-sendbird-signature');
const hash = crypto.createHmac('sha256', API_TOKEN).update(body).digest('hex');
req.body = JSON.parse(req.body);
signature == hash ? res.send(200) : res.send(401);
});
app.listen(PORT, () => {
console.log(`App is listening on port ${PORT}`);
});
期待した値でリクエストが飛んでこない
APIを呼び出す側の引数にはしっかりとJSON文字列を設定して呼び出しており、
Cloud Functions側ではその値が飛んでくるはずですが、飛んでこない...
console.log()
など仕込んでデバッグしてみると、JSONオブジェクト
で来ているっぽかった
ここはなんでなのかはわかりませんでしたが、とりあえずどうするか...?
(わかる方いたら教えて頂きたいです...🙇♂️)
TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received an instance of Object
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = 5001;
const API_TOKEN = 'SENDBIRD_MASTER_API_TOKEN';
app.post('/sendbird-xsig', express.text({ type: 'json' }), (req, res) => {
const body = req.body;
const signature = req.get('x-sendbird-signature');
const hash = crypto.createHmac('sha256', API_TOKEN).update(body).digest('hex');
// update()メソッドにて以下のエラーが発生する
// TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or
// an instance of Buffer, TypedArray, or DataView. Received an instance of Object
req.body = JSON.parse(req.body);
signature == hash ? res.send(200) : res.send(401);
});
app.listen(PORT, () => {
console.log(`App is listening on port ${PORT}`);
});
JSON文字列で受け付けているが...
express.text({ type: ~ })
というミドルウェア関数の一つであり、
type: ~
に設定したContent-Typeの値を文字列として扱う挙動をするがそれが、
機能していないのか他要素で邪魔されているのかが不明
今回は、express.text({ type: json })
となっており
Content-Type: application/json
は文字列で受け取るようになっているはず
対策として、verify
オプションを使用すると良い
一旦対策としては、Express側ではverify
オプションを使用するといいらしい
verify
では、verify(req, res, buf, encoding)
のように、
引数にbufferデータを持つことができるので、
update()
メソッドは string
か buffer
を引数で受け付けているので今回は buffer
を渡すことに切り替えました
app.use(express.json({
verify: (req, _res, buf, _encoding) => {
const signature = req.headers['x-sendbird-signature']
const hash = crypto.createHmac('sha256', apiToken).update(buf).digest('hex')
if (signature !== hash) {
console.log('unmatched!')
}
}
}))
無事、ハッシュ値を検証できてデータ改ざんチェックを行うことができました!🎉
宣伝
自分が所属している会社についても紹介させてください 🙇♂️
弊社では物流課題解決や荷物を配送するドライバーの価値を向上させるために
「ピックゴー」 というサービスを運用しています
(ファイルアップロード制限?かかって画像アップロードできない...!ごめんなさい...🙇♂️)
代わりに弊社HPを...
あと、那覇Baseが本店になりました
私が在籍している那覇Baseが本店に移転しました🎉
エンジニアも募集中です!
気になる方は会社説明会も実施してますので気軽にお話聞きに来てくれたら嬉しいです😎