経緯
とあるプロジェクトでChatworkとの連携機能を設計することになり、PoC(概念実証)を行いました。検証の過程でローカル環境を汚したくなかったのと、HTTPSサーバ環境をサクッと構築したかったのでDockerを使うことにしました。
準備
Chatwork OAuthクライアントの作成
Chatworkのサービス連携 > OAuth からOAuthクライントを作成します。
クライアント名はお好みで設定して構いません。
リダイレクト先URLにはこれから作成するlocalhostサーバのURL(https://localhost:3443/oauth )を入力します。
クライアントの作成が完了すると、クライアントIDとクライアントシークレットが発行されます。これらは後ほど使いますので控えておきます。
docker-compose.ymlの作成
手軽にHTTPSサーバ環境を構築するために https-portal というコンテナを使用します。https-portal は自己署名証明書(オレオレ証明書)の発行までやってくれるのでとても便利です。
アプリケーションサーバには Express.js を使用するのでNode.jsのコンテナを使用します。プロジェクトのサーバ環境がGoogle Cloud PlatformなのでNode.jsのバージョンは12を選びました。
docker-compose.yml
version: '3'
services:
https-portal:
image: steveltn/https-portal:1
ports:
- '3443:443'
environment:
STAGE: local
DOMAINS: 'localhost -> http://app:3000'
volumes:
- ./https-portal-data:/var/lib/https-portal
app:
image: node:12
env_file: ./app.env
environment:
- TZ=Asia/Tokyo
- DEBUG=app:*
tty: true
volumes:
- ./src:/app
working_dir: /app
command: npm start
Express.jsの環境構築
express-generatorで雛形を生成
コンテナのコンソールからExpress.js の雛形を生成します。
今回はviewにEJSを使用するので、オプションに--view=ejs
を加えます。
また、ChatworkのAPIを実行するためにaxiosを使用するのでパッケージをインストールしておきます。
$ docker-compose run --rm app bash
$ npx express-generator --view=ejs
$ npm i axios --save
$ npm install
$ exit
環境変数の定義
app.env
Express.jsで使用する環境変数を定義します。ルームIDはブラウザのURLから取得できます。
# クライアントID
CLIENT_ID=[OAuthクライアント作成で生成したクライアントID]
# クライアントシークレット
CLIENT_SECRET=[OAuthクライアント作成で生成したクライアントシークレット]
# メッセージのテスト送信に使用するRoom ID
TEST_ROOM_ID=999999999
OAuthリダイレクト用のRouterを追加
今回は /oauth というパスを用意することにしました。
src/app.js
...
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var oauthRouter = require('./routes/oauth'); // <- 追加
...
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/oauth', oauthRouter); // <- 追加
/oauth ではアクセストークンの取得とテストメッセージの送信を直列で行うようにします。実際は取得したトークンをDBへ保存したりする処理が必要になりますが、PoCなのでそこまでは実装しません。
src/routes/oauth.js
const express = require('express');
const router = express.Router();
// axios
const axiosBase = require('axios');
const axios = axiosBase.create({
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest',
},
responseType: 'json',
});
router.get('/', async function (req, res, next) {
let isSuccess = false;
try {
/**
* Access Tokenを取得する
*/
var tokenParams = new URLSearchParams();
tokenParams.append('grant_type', 'authorization_code');
tokenParams.append('code', req.query.code);
const authKey = Buffer.from(
process.env.CLIENT_ID + ':' + process.env.CLIENT_SECRET,
'utf-8'
).toString('base64');
const tokenResult = await axios.post(
'https://oauth.chatwork.com/token',
tokenParams,
{
headers: {
Authorization: `Basic ${authKey}`,
},
}
);
const token = tokenResult.data.access_token;
/**
* テストメッセージを送信する
*/
var messageParams = new URLSearchParams();
messageParams.append('body', 'Hello!');
const messageResult = await axios.post(
`https://api.chatwork.com/v2/rooms/${process.env.TEST_ROOM_ID}/messages`,
messageParams,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
console.log(messageResult.data);
isSuccess = true;
} catch (error) {
console.error(error);
isSuccess = false;
}
res.render('oauth', {
isSuccess,
});
});
module.exports = router;
結果表示用のviewファイルも追加します。
src/views/oauth.ejs
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<% if (isSuccess) { %> Success!! <% } else { %> failure... <% } %>
</body>
</html>
動かしてみる
ターミナルからdocker-compose を実行します。ログが見えるようにフォアグラウンドで実行することにします。
$ docker-compose up
全てのコンテナが問題なく起動したら、下記のURLからOAuth認証フローを開始します。stateはPoCでは使用しないので適当な文字列を設定しておきます。
https://www.chatwork.com/packages/oauth2/login.php?response_type=code&client_id={client_id}&state={state}&scope=offline_access%20rooms.messages:write
「許可」ボタンをクリックします。
ブラウザにSuccess!!と表示されていればメッセージの送信まで成功していますのでChatworkのチャンネルを確認してみましょう。
チャンネルにメッセージが送信されていることが確認できました。
まとめ
Dockerとhttps-portalを使うことで面倒なHTTPSサーバの準備も極めて簡単に構築することができました。OAuth認証のリダイレクトURLにはHTTPSプロトコルを要求されるケースが多いので、他のサービス連携の調査にも活用できると思います。