Help us understand the problem. What is going on with this article?

LINE Messaging APIでX-Line-Signatureの署名検証を行う(AzureFunctions/Node.js)

More than 3 years have passed since last update.

はじめに

前回、Azure Functionsを使ってLINE Messaging APIを使ったbotを作成しました。
Azure Functionsを使って非同期処理のLINE BOTを作成する
スクリーンショット 2016-10-13 1.16.32.png

前回の残作業として、以下の内容が残っていました。

紹介したコードは最小限のロジックのみ実装していてセキュリティやエラー処理などは考慮していません。
例えば、LINEサーバーからのメッセージであることの検証などが不足しています。

今回はメッセージの署名検証を行ってみます。

署名検証について

LINE developersのAPI Referenceに次の記述があります。

Webhook Authentication

X-Line-Signature Request Headerに入っているSignatureを検証することによって、
リクエストがLINE Platformから送信されたものであることを確認する必要があります。

検証は以下の手順で行います。

1. Channel Secretを秘密鍵として、HMAC-SHA256アルゴリズムによりRequest Bodyのダイジェスト値を得る。
2. ダイジェスト値をBASE64エンコードした文字列が、Request Headerに付与されたSignatureと一致することを確認する。

https://devdocs.line.me/ja/#webhook-authentication

つまり、用意したWebhookへのアクセスが本当にLINEのサーバーからのそれなのかチェックする必要があります。
検証手順も記載されているのでその通りに実装して見ます。

実装

Webを検索すると類似の実装をやっている例がありました。
【乗るしかない】AWS LambdaとLINE BOT APIでRSSを通知する【このビッグウェーry】 | Developers.IO

昔のLINE APIを使う際の認証のためのコードのようですが、やっていることはほとんど同じです。上記のサイトを参考にして実装したのがこちらです。

var crypto = require("crypto");

function validate_signature(signature, body)
{
    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
    return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }

    context.res = { body : "" };
    context.done();
};

yorifuji/azure-functions-line-signature-bot

個別に見ていきます。

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }

    context.res = { body : "" };
    context.done();
};

まずLINEサーバーからのリクエストが届くと、こちらの関数が実行されます。
署名検証のために新たに追加したのはこちらのコードです。

    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }

validate_signatureにreq.headers['x-line-signature']とreq.bodyを渡しています。
req.headers['x-line-signature']にはLINEサーバから付与されたSignatureが入っています。
req.bodyはRequest Bodyそのものです(JSON)。

次にvalidate_signatureの実装はこちらです。

var crypto = require("crypto");

function validate_signature(signature, body) {
    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
    return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}

先頭の var crypto... はNode.jsのcryptoを利用するための宣言です。HMAC-SHA256を求めるのに利用します。
validate_signatureの先頭でLINE_CHANNEL_SECRETを環境変数(process.env)から読み込んでいます。

    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;

これはHMAC-SHA256の秘密鍵として利用します。LINE_CHANNEL_SECRETの値はLINE Developersの管理画面に表示されるのでそちらからコピーしておきます。
環境変数から読み込んでいるのはGitHub上でコードを管理して、それを直接Azureにデプロイする運用にしているためです。

署名検証の手順としては、crypto.createHmacで生成したインスタンスに対してupdate関数を使ってbodyの内容をセットします。最後にBASE64に変換した値を出力します。
出力した値とsignatureが一致していれば署名検証に成功です。

サンプルコード

GitHubはこちらです。yorifuji/azure-functions-line-signature-bot

前回と同様に、Azure Functionsで「継続的インテグレーションの構成」の設定を行なっている方は、リポジトリをこちらに切り替えていただくとソースコードを丸ごと切り替えることができます。

なお、署名検証のために以下のキーを環境変数に登録する必要があります。

キー:
LINE_CHANNEL_SECRET
値:
LINE developersのChannel Secretの値

まとめ

LINE Messaging APIでX-Line-Signatureの署名検証を行いました。内容に誤りがあればコメントいただけると幸いです。

予定

  • メッセージの種類に応じて応答を変える
yorifuji
通信系SIerでリアルタイム映像通信システムの管理業務の傍ら個人開発をしています。最近はiOSアプリやWebRTCを勉強中。Apple製品が好き。フォローやコメント貰えると嬉しいです😊https://note.com/yorifuji
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした