-
-
Save jirawatee/366d6bef98b137131ab53dfa079bd0a4 to your computer and use it in GitHub Desktop.
let text = JSON.stringify(req.body) | |
text = text.replace(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, (e) => { | |
return "\\u" + e.charCodeAt(0).toString(16).toUpperCase() + "\\u" + e.charCodeAt(1).toString(16).toUpperCase() | |
}) | |
const signature = crypto.createHmac('SHA256', LINE_CHANNEL_SECRET).update(text).digest('base64').toString() | |
if (signature !== req.headers['x-line-signature']) { | |
return res.status(401).send('Unauthorized') | |
} |
When validating a webhook payload with x-line-signature
, you should not parse original request and use JSON#stringify
to get original request from object again🙅. The signature validation must be done without making any changes to the received data.
LINE's webhook does not escape data on LINE Platform, nor does it force the webhook server to use such workarounds. If signature validation fails, it could be due to one of the following reasons: (your webhook server has issue or there are malicious requests)
- A malicious request is sent to your server.
- The webhook is incorrectly transformed before verification.
- The channel secret has expired.
At least, this issue has not been observed when I create a Node.js server on Cloudflare. This suggests that the problem might be specific to certain serverless environments or just application.
Incorrect Transformation with JSON.stringify
For example, if the received webhook data is "{\"a\": \"b✊🤨\"}"
, parsing and then stringifying may not restore the exact same string(=original request payload). In other words, "{\"a\": \"b✊🤨\"}"
and JSON.stringify({"a": "b✊🤨"})
may not match. Additionally, if the serverless environment transforms the request before your application starts requests, it can lead to signature validation failure.
In a Node.js environment, using Express(https://github.com/expressjs/express) along with body-parser(https://github.com/expressjs/body-parser) is one option to solve this issue. You need to verify the received webhook payload without transforming it. This can be achieved with Express, Hono(https://github.com/honojs/hono), or other libraries(or without any library) as long as the webhook is not transformed.
You can also read line/line-bot-sdk-nodejs#922 (comment)
@Yang-33 I already found the problem. The problem is the type of request.body from Cloud Functions for Firebase is not the Buffer but it is the Object instead.
After exploring I figure out in how to fix it by asking Gemini. It tell me that in Cloud Functions for Firebase I can get the Buffer from req.rawBody.
Therefore, the code above should be modified to
const signature = crypto.createHmac('SHA256', CHANNEL_SECRET.value()).update(req.rawBody).digest('base64').toString();
if (signature !== req.headers['x-line-signature']) {
return res.status(401).send('Unauthorized')
}
Thank you so much for your comment. I appreciate it.
Thanks for the video & this gist, it helped solve my issue
The above code is a little broken though, it tries to escape 2 characters in all cases, even though the regex can match single unicode characters. This would result in NaNs polluting the string and causing the incorrect signature to be generated.
This replacer arrow function should work better:
Also, I'm not sure exactly what characters you specifically seek to match, but if it's emojis, using the built in unicode matchers might be cover more cases, i.e.
/\p{Emoji_Presentation}/gu