JWT認証を導入したAPI実装 - 自作サービスづくり10
今回もWeb開発に関する記事です。
前回はデザイン改善とSlackとの連携機能の実装を行いました。
developer-bpeldi2oerkd8.hatenablog.com
ここでは、ロゴの作成とSlack連携登録・編集機能を作ったため、
今回はいよいよJWT認証を導入したAPIの実装に移ります。
構成
構成は以下のようになっています。
手順としては、
- /api/v1/login にアクセスし、lattendanceにJWTをリクエスト
- lattendance上でJWTを発行し、JSONでJWTを返す
- /api/v1/schedulesにアクセス時(以前実装したAPIの利用時)にヘッダーに発行したJWTを追加しリクエスト
- 送られてきたJWTをもとに認証し、OKの場合のみ結果を返す(NGの時はエラー内容を返す)
順番に実装していきます。
JWTの発行
JWTの発行・認証時に jsonwebtoken を用います。
ドキュメントを見ると、JWTを発行するために jwt.sign() 、JWTを検証する ためにjwt.verify()を用いることが書いてあります。
これに従って、まずJWTの発行を実装します。
まず、JWTを使用できるようにするため、以下のようにapp.jsを変更します。
app.use(express.urlencoded({ extended: true }));
また、APIのログイン部分は以下の通りです。
事前に発行したチャンネルトークンとチャンネルIDをPOSTし、JWTを返します。
'use strict'; const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const Room = require('../../models/room'); //APIのシークレットキー const serverAPISecret = process.env.SERVER_API_SECRET || require('../../secret_info/server_api_info'); router.post('/', (req, res) => { //roomIDとroomTokenを取得 const roomId = req.body.roomId; const roomToken = req.body.roomToken; Room.findByPk(roomId) .then((room) => { if(room && room.roomToken === roomToken) { //JWTを生成 const token = jwt.sign({roomId: roomId}, serverAPISecret, {expiresIn: '1h'}); res.json({ status: 'OK', data: { token: token } }); } else { res.json({ status: 'NG', error: { messages: ['認証エラー'] } }); } }); }); module.exports = router;
JWTの認証
今度はJWTの認証を入れます。
認証を行う関数をverifyToken()とし、ミドルウェアとして引数に入れます。
verifyTokenの実装はこちらです。
'use strict'; const jwt = require('jsonwebtoken'); //APIのシークレットキー const serverAPISecret = process.env.SERVER_API_SECRET || require('../../secret_info/server_api_info'); function verifyToken(req, res, next) { const authHeader = req.headers["authorization"]; //HeaderにAuthorizationがあるかチェック if (authHeader) { //Bearerのチェック if (authHeader.split(" ")[0] === "Bearer") { try { const decoded = jwt.verify(authHeader.split(" ")[1], serverAPISecret); const roomId = req.params.roomId; //roomIdの検証 if (decoded.roomId === roomId) { next(); } else { res.json({ status: 'NG', error: { messages: ['検証エラー'] } }); } } catch (e) { res.json({ status: 'NG', error: { messages: ['Tokenエラー'] } }); } } else { res.json({ status: 'NG', error: { messages: ['ヘッダー形式エラー'] } }); } } else { res.json({ status: 'NG', error: { messages: ['ヘッダーエラー'] } }); } } module.exports = verifyToken;
これを以下のようにミドルウェアとして挟むことで認証を入れています。
router.post('/:roomId/users/:slackId/dates/:dateString', verifyToken, (req, res, next) => {
APIのテスト
APIが正しく実装されているかテストします。
今回はJestではなく、VS Code上で手軽にREST APIのテストができるREST Clientを使います。
使い方はこちらのページが参考になります。
qiita.com
では、実際にリクエストを書いていきます。
# JWT発行テスト POST http://localhost:8000/api/v1/login Content-Type: application/json { "roomId": "TEST0001", "roomToken": "事前に発行したチャンネルトークン" } ### # 認証がない場合の出欠確認テスト(認証エラー) GET http://localhost:8000/api/v1/schedules/TEST0001/users/{ユーザーID}/dates/2021-07-02 ### # 認証がある場合の出欠確認テスト GET http://localhost:8000/api/v1/schedules/TEST0001/users/{ユーザーID}/dates/2021-07-02 Authorization: Bearer {先ほど返ってきたJWT} ### # 認証がある場合の出欠確認テスト GET http://localhost:8000/api/v1/schedules/TEST0001/users/{ユーザーID}/dates/2021-07-03 Authorization: Bearer {先ほど返ってきたJWT} ### # 認証がある場合の出欠更新テスト POST http://localhost:8000/api/v1/schedules/TEST0001/users/{ユーザーID}/dates/2021-07-02 Authorization: Bearer {先ほど返ってきたJWT} Content-Type: application/json { "availability": 2 }
正しいJSONが返ってきました。