2
0

More than 1 year has passed since last update.

LINE WORKSからのcallbackメッセージを判定するExpressミドルウェア

Posted at

背景

LINE WORKSのbotサーバを開発する際,受信したユーザからの応答(callback)が正しくLINE WORKS側から送信されたものなのか(改竄されていないか)を判定する必要があります。LINE WORKSの公式ページにはJavaのサンプルコードはあるのですが,JavaScriptのものは無かったため自分で実装しました。さらにこれをExpressのミドルウェアとして利用できるようにしましたので共有します。

環境

  • Node v16.13.0
  • npm 8.1.4
  • Express 4.15.2

公式ページから

上述の公式ページには以下のような記述があります。

Bot サーバーが受信した HTTP POST リクエストは、LINE WORKS プラットフォームから送信されていない危険なリクエストの可能性があります。 必ず署名を検証してから、イベントオブジェクトを処理してください。

メッセージサーバーから送信されたメッセージの改ざん有無を確認するには、ヘッダーに含まれる X-WORKS-Signature を用います。 確認プロセスは以下を参照してください。

  1. 「Developers Console > Bot」Botの詳細情報画面でBot Secretを取得します。

  2. Bot Secretを秘密鍵として利用し、メッセージサーバーから送られた body の内容を HMAC-SHA256 アルゴリズムでエンコードします。

  3. 上記の HMAC-SHA256 アルゴリズムでエンコードした結果を BASE64 エンコードします。

  4. X-WORKS-Signature のヘッダー値と比較し、同一であればメッセージは改ざんされていないと判断できます。

では,Node.jsのデフォルトモジュールであるcryptoモジュールを利用してコードを実装していきます。

コード

まずbotのidとsecretをまとめたJSONを用意しておきます。

const botSecrets = {
  12345678: 'abcdefghijklmn',
  23456789: 'opqrstuvwxyz'
}

実際にはこのようにハードコーティングするのでなく,.envファイル等で管理した方が良いでしょう。

実際のミドルウェアのコードがこちらです。

const crypto = require('crypto')

const worksHeaderCheck = function (req, res, next) {
  const body = JSON.stringify(req.body)
  const botId = req.header('X-WORKS-BotId')
  const signature = req.header('X-WORKS-Signature')
  if (!botId || !signature) return next(new CustomError('Header malformed.', 400))
  const secret = botSecrets[botId]
  if (secret === undefined) return next(new CustomError('Bot secret is undefined', 500))
  const hash = crypto.createHmac('sha256', secret).update(body).digest('base64')
  if (signature !== hash) return next(new CustomError('Signature is altered.', 401))
  next()
}

まずヘッダのX-WORKS-BotIdおよびX-WORKS-Signatureに含まれるbotのidと署名を取り出します。どちらかが欠損していればヘッダ情報不良として400エラーを返します。

次にbotのsecretが登録されているか判定し,登録がなければ500エラーを返します。

最後に送信されたbodyと登録されていたbot secretからハッシュを計算し,送信されたsignatureと比較します。ハッシュ計算の際には最初からbase64形式で求めることに注意しましょう。合致しなければ改竄されていることになりますので,401エラーを返します。

なおCustomErrorはエラーハンドリングをシンプルにするためのカスタムエラークラスです。azujuuuuuun様のエントリを参考にさせて頂きました。

// エラーハンドリング用カスタムクラス
class CustomError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.statusCode = statusCode
  }
}

// エラーハンドリング用ミドルウェア
const errorHandler = function (err, req, res, next) {
  console.error(err.message)
  res.status(err.statusCode || 500).send(err.message)
}

使用法

express()
  .use(express.urlencoded({ extended: true }))
  .use(express.json())
  .get('/hoge', worksHeaderCheck, (req, res) => {
    // 処理
  })
  .use(errorHandler)
  .listen(PORT, () => console.log(`Server started and listening on ${ PORT }`))
2
0
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
2
0