KARTE の Webhook では、データ改ざんの検知や送信者の確認 (なりすまし防止) のために署名をヘッダーに添付して送信しています。署名にはアプリ作成時に発行されたシークレットキーを使用して生成します。
署名の検証
KARTE から送信されるリクエストには署名として X-Karte-Signature
HTTP ヘッダーが含まれています。このヘッダーには HMAC-SHA256 キー付きのハッシュがついています。このハッシュを用いて検証を行います。
署名を検証するには、X-Karte-Request-Timestamp
HTTP ヘッダーの値とリクエストの本文をコロン(:
)で連携した文字列に対して、アプリに発行されるシークレットキーを使用して、SHA256 ハッシュを計算します。この計算した結果と、X-Karte-Signature
HTTPヘッダーの値が一致するかで検証を行います。(具体的な検証手順は後述の「検証手順」をご確認ください)。
また、 X-Karte-Request-Timestamp
が有効期限が切れていないかも検証することで、リプレイ攻撃を防ぐこともできます。 X-Karte-Request-Timestamp
は Unix時間 で表され、Webhook のリクエストが送信される直前のタイムスタンプとなります。
Webhook の HTTP ヘッダーに含まれる値
X-Karte-Signature
: HMAC-SHA256 キー付きのハッシュX-Karte-Request-Timestamp
: Webhook リクエスト送信時のタイムスタンプ(秒)
検証手順
-
「API v2 設定」で作成したアプリから「Client Secret」を取得します。ハッシュを計算するときのキーとなります。ここでは例としてシークレットの値を「
KarteClientSecret
」とします。
【「API v2 設定」の起動経路】グローバルメニュー > ストア > API v2 設定 -
Webhook で送信されたリクエストの HTTP ヘッダーから
X-Karte-Request-Timestamp
の値を取得します。例として、取得したタイムスタンプは1612240200
とします。 -
タイムスタンプが一定時間以上経過していないことを確認します。
有効期限を5分とした場合、現在時刻の Unix 時間が 、1612240200
に300(5分を秒に換算)
を足し合わせた数を超えていないかをチェックします。経過していた場合はリクエストを破棄します。 -
タイムスタンプとリクエストの本文をコロン(
:
)を使って連結し署名対象文字列を作成します。
リクエストの本文を{"user_id":XXXX,"api_key":XXXX}
とした場合、署名対象文字列は1612240200:{"user_id":XXXX,"api_key":XXXX}
となります。タイムスタンプ、リクエストの本文の順で連結をします。 -
署名対象文字列に対して、シークレットキーを使用して、SHA256 ハッシュを計算をします。計算したハッシュは Base64 でエンコーディングします。例で上げた値の場合、
OTBjNDJhYjgyZTY4Zjg5ZmU3YWZjNDc4NWZlZDM2NGUzMmMyMjMwMjdjOWEzMDg1YzUyN2YwYjViNTAwNTFmOA==
のような形で出力されます。 -
生成したハッシュと
X-Karte-Signature
HTTP ヘッダーの値が一致することを確認します。一致したものは KARTE から送られてきたリクエストと判断できます。
サンプルコード
const crypto = require('crypto');
const secret = 'YOUR APP SECRET';
const requestTimestamp = req.getHeader('X-Karte-Request-Timestamp');
// 有効期限の検証(5分以内)
if ((parseInt(requestTimestamp, 10) + 5 * 60) * 1000 < new Date().getTime()) {
throw new Error('expired timestamp');
}
const signature = req.getHeader('X-Karte-Signature');
if (!signature) {
throw new Error('not authorized');
}
const reqBody = JSON.stringify(req.body);
const stringToSign = requestTimestamp + ':' + reqBody;
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(stringToSign).digest('base64');
if (signature !== digest) {
throw new Error('invalid auth');
}