讃岐小僧のEngineering×Techメモ

プログラミングや、趣味の野球、資産運用について、その他、ビジネスやテクノロジーをテーマに様々なことをつぶやく場所です。

【LINE WORKS】Callbackサーバーに登録する秘密鍵の取り扱いで少しハマりました

今回の内容

2021年12月1日、API廃止の関係で動かなくなるかもしれないので、下記のリンクにしたがって修正してください。

こちらのブログでも修正しておきます。

developers.worksmobile.com

現在、トライアルでLINE WORKSのトークBotを作成しています。
プロジェクトはNode.js + Herokuで進めています。

トークBotを作成する際にトークBotのメッセージを受信し、callbackするサーバーに秘密鍵の情報を登録しなければいけないのですが、LINE WORKSの公式ドキュメントに掲載されている認証キーのファイルは下図のような感になっているのに対して

-----BEGIN RSA PRIVATE KEY-----
aaa
bbb
ccc
-----END RSA PRIVATE KEY-----

f:id:keisuke8925gdk:20190514145509p:plain

私がダウンロードしているxxxxxx.keyファイルは

-----BEGIN PRIVATE KEY-----
aaa
bbb
ccc
-----END PRIVATE KEY-----

f:id:keisuke8925gdk:20190514150404p:plain

のようになっている。

あれ、なんか違う。

-----BEGIN RSA PRIVATE KEY-----
aaa
-----END RSA PRIVATE KEY-----


↓私のファイル

-----BEGIN PRIVATE KEY-----
aaa
-----END PRIVATE KEY-----

ということで調べてみるとやはり、ダウンロードした.keyファイルのエンコード方式が違う、もともとはおそらく、ただの平文だったor Der方式だった??ようでPEMでエンコードできるように、下記コマンドを実行して、環境変数に登録したところ上手く動くようになりました。

openssl rsa -in <my file name>.key -out <my file name>.key -outform pem

opensslコマンドについての簡易なメモ

openssl rsaコマンドはRSA秘密鍵を使った処理を行うもの。

Herokuの環境変数への登録

今回は秘密鍵の内容をHerokuの環境変数へ登録して利用したいため下記のコマンドで登録しようとしたのですが、改行やスペースがあると上手く扱えないようです。

heroku config:set =""


そのため今回はいさぎよく諦めてHerokuの画面から登録しました。

Herokuのsettings > config varのところで直接書き込みしました。

f:id:keisuke8925gdk:20190515155238p:plain

今回のサンプル貼っておきます。

"use strict";

// モジュールインポート
const express = require("express");
const server = express();
const bodyParser = require("body-parser");
const jwt = require('jsonwebtoken');
const https = require("https");
const request = require("request");
const qs = require("querystring");

//変数
const APIID = process.env.APIID;
const SERVERID = process.env.SERVERID;
const CONSUMERKEY = process.env.CONSUMERKEY;
const PRIVATEKEY = process.env.PRIVATEKEY;
const BOTNO = process.env.BOTNO;

server.use(bodyParser.json());

// Webアプリケーション起動
server.listen(process.env.PORT || 3000);

// サーバー起動確認
server.get('/', (req, res) => {
    res.send('Hello World!');
});

// Botからメッセージに応答
server.post('/callback', (req, res) => {
    res.sendStatus(200);

    const message = req.body.content.text;
    const roomId = req.body.source.roomId;
    const accountId = req.body.source.accountId;


// ここが認証しているところ
// ここに詳しく書いてある
// https://developers.worksmobile.com/jp/document/1002002?lang=ja

    getJWT((jwttoken) => {
        getServerToken(jwttoken, (newtoken) => {
            sendMessage(newtoken, accountId, message);
        });
    });
});

//1. サーバーAPI用JWT取得
function getJWT(callback){
    const iss = SERVERID;
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + (60 * 60); //JWTの有効期間は1時間
    const cert = PRIVATEKEY;
    const token = [];
    const jwttoken = jwt.sign({"iss":iss, "iat":iat, "exp":exp}, cert, {algorithm:"RS256"}, (err, jwttoken) => {
        if (!err) {
            callback(jwttoken);
        } else {
            console.log(err);
        }
    });
}


//2. サーバートークンの取得
function getServerToken(jwttoken, callback) {
    const postdata = {
        url: 'https://authapi.worksmobile.com/b/' + APIID + '/server/token',
        headers : {
            'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
        },
        form: {
            "grant_type" : encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            "assertion" : jwttoken
        }
    };
    request.post(postdata, (error, response, body) => {
        if (error) {
            console.log(err);
            callback(err);
        } else {
            const jsonobj = JSON.parse(body);
            const AccessToken = jsonobj.access_token;
            callback(AccessToken);
        }
    });
}

//sendMessageを利用したメッセージ送信
function sendMessage(token, accountId, message) {
    const postdata = {

        //2021年12月1日修正
        url:'https://apis.worksmobile.com/r/' + APID + '/message/v1/' + BOTNO +  '/message/push’

        /*ここから
        url: 'https://apis.worksmobile.com/' + APIID + '/message/sendMessage/v2', 
         ここまで削除 廃止APIのため*/
        headers : {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : CONSUMERKEY,
          'Authorization' : "Bearer " + token
        },
        json: {
            "botNo" : Number(BOTNO),
            "accountId" : accountId,
            "content" : {
                "type" : "text",
                "text" : message
            }
        }
    };
    request.post(postdata, (error, response, body) => {
        if (error) {
          console.log(error);
        }
        console.log(body);
    });
}

参照