はじめに

Node.js, express, axios で JWT(JSON Web Tokens) を利用する方法を整理した。

ざっくり以下の流れ。

  • ログイン時にサーバで Token を 発行/送付
  • クライアントが API 利用時に Token を Request Headers なり Body なりで送付
  • サーバ側で Token の正当性を検証

TL;DR

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. 前置き
      1. JWT(JSON Web Tokens) とは
      2. express で JWT を使う方法
    2. auth0/node-jsonwebtoken の使い方
      1. セットアップ
      2. JWT の発行
      3. JWT の利用
      4. JWT の検証
  5. まとめ
  6. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"

$ node -v
v12.16.1

$ npm -v
6.13.4

$ npm ls express
express@4.17.1 | MIT | deps: 30 | versions: 264

$ npm ls jsonwebtoken
jsonwebtoken@8.5.1 | MIT | deps: 10 | versions: 78

詳細

全体の流れ

  • ログイン時にサーバで Token を 発行/送付
  • クライアントが API 利用時に Token を Request Headers なり Body なりで送付
  • サーバ側で Token の正当性を検証

前置き

JWT(JSON Web Tokens) とは

詳しくは参考サイト参照。

express で JWT を使う方法

express で JWT を使う主な方法として、auth0/express-jwtauth0/node-jsonwebtoken とがある。

今回は以下の理由から auth0/node-jsonwebtoken を使った。

  • Token に有効期限を持たせたい
  • 有効期限切れや検証エラー時の挙動を細かく制御したい
  • ↑の方法を auth0/express-jwt の Document から見つけきれず

auth0/node-jsonwebtoken の使い方

セットアップ

npm i でインストール

1
$ npm i jsonwebtoken

JWT の発行

sign(payload, secretOrPrivateKey, [options, callback]) で JWT の発行。

  • payload: 暗号化対象
  • secretOrPrivateKey: Token の 暗号化/検証 に使うキー
  • options: アルゴリズム(algorithm)とか期限(expiresIn)などを設定可能
1
2
3
4
5
6
7
const jwt = require('jsonwebtoken');
const user_id = 1;
const token = jwt.sign(
{ user_id: user_id },
'secret',
{ expiresIn: '1h' } // 単位無しだとミリ秒になる
);

expiresIn の時間指定は zeit/ms の記法が利用可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
// https://github.com/zeit/ms#examples より
'2 days'
'1d'
'10h'
'2.5 hrs'
'2h'
'1m'
'5s'
'1y'
'100'
'-3 days'
'-1h'
'-200'

ログイン時に JWT を 発行/送付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const jwt = require('jsonwebtoken');

const login = async (req, res, next) => {
// ログインに必要な検証
// ...

const user_id = 1; // 実際は適切なデータを使う
const body = { user_id: 1 }; // response body

// JWT 発行
const token = jwt.sign({ user_id }, 'secret', { expiresIn: '1h' });
// JWT 付きで返却
body.token = token;

return res.status(200).send(body);
};

router.post('/login', login);

JWT の利用

ログイン成功時に受け取った JWT を Store あたりに保持しておく。

以下は Request Headers の AuthorizationBearer <token> で送る場合の例。

(見ればわかるが) API 実行は axios/axios を使っている。

1
2
3
4
5
6
7
8
9
10
11
const axios = require('axios');

const postExample = async () => {
const params = { msg: 'hello' };
const Authorization = `Bearer ${this.$store.state.jwt}`;
const res = await axios.post(
'/path/to/api',
params,
{ headers: { Authorization } }
);
};

Token の送付方法に関しては トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた - Qiita も合わせて見ておいた方が良い。

JWT の検証

検証自体は verify(token, secretOrPublicKey, [options, callback]) で行う。

1
2
3
4
5
6
7
8
9
10
11
const jwt = require('jsonwebtoken');
const token = 'xxxx';
let decoded = '';
try {
decoded = jwt.verify(token, 'secret');
} catch (e) {
console.error({e});
if (e.response && e.response.status === 401) {
/* JWT 検証エラー(期限切れなど)時の処理 */
}
}

Request Headers の Authorization: Bearer <token> で送られてきた場合の例。

1
2
3
4
5
6
7
8
9
10
11
12
13
const jwt = require('jsonwebtoken');

const hogeApi = async (req, res, next) => {
const token = req.headers.authorization.split(' ')[1]});
try {
jwt.verify(token, 'secret');
} catch (e) {
console.error({e});
}
...
};

router.post('/hoge', hogeApi);

↑のやり方だと、全ての API に Token の検証処理を実装しないといけないので、 use で別途検証処理を定義する。

検証処理以前に定義した API は JWT 検証処理は無し。検証処理以降に定義した API は JWT 検証処理が行われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// JWT 検証が不要なものは検証処理前に定義
router.post('/login', () => { /* login */ });
router.post('/logout', () => { /* logout */ });

// JWT 検証
router.use((req, res, next) => {
// Authorization 未設定
if (!req.headers.authorization) {
const data = { message: 'Authorization required' };
return res.status(401).send(data);
}

const token = req.headers.authorization.split(' ')[1];
let decoded;
try {
// 検証
decoded = jwt.verify(token, 'secret');
} catch (err) {
console.error({err});
const data = { message: `Invalid token: ${err.message}` };
return res.status(401).send(data);
}

// 必要に応じて decode 後のデータの検証
if (!decoded || !decoded.user_id || decoded.user_id !== req.body.user_id) {
const data = { message: `Invalid token or user_id unmatch: ${decoded.user_id}, ${req.body.user_id}` };
return res.status(401).send(data);
}

next();
});

// JWT 検証が必要なものは検証処理後に定義
router.post('/hoge', () => { /* 処理 */ });
router.post('/fuga', () => { /* 処理 */ });

まとめ

参考文献

関連記事

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list