【JWT】 入門

# JWTとは
- [公式サイト](https://jwt.io/)
- *JSON Web Token*の略　　
- 電子署名により、改ざん検知できる。
- 認証用のトークンなどで用いられる。

# 構成
- `ヘッダ`、`ペイロード`、`署名`の3つから成る。
- それぞれは、`Base64`でエンコードされている
- それぞれは、 `. (ドット)` で結合されている。

```none:JWTの構文
[ヘッダ].
[ペイロード].
[署名]
```

```none:実際のJWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
t42p4AHef69Tyyi88U6+p0utZYYrg7mmCGhoAd7Zffs
```

# 作り方

　1.　`アルゴリズム`と`Tokenのタイプ`を、ヘッダに設定

| アルゴリズム      | 説明                        |
|:---------------|:----------------------------|
| *HMAC SHA-256* | 256ビットのハッシュ値を生成する関数 |

```json:ヘッダ
{
  "alg": "HS256",
  "typ": "JWT"
}
```

　2.　ヘッダをBase64でエンコード

```sh:ヘッダをBase64でエンコード
echo -n '{"alg":"HS256","typ":"JWT"}' | base64
```

```none:Base64エンコードされたヘッダ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

　3. ペイロードを設定

| クレーム名     | 説明                        |
|:------------|:----------------------------|
| *issued at* | JWT を発行した時刻             |
| *subject*   | JWT の主語となる主体の識別子     |

```json:ペイロード
{
  "sub": "1234567890",
  "iat": 1516239022
}
```
    
　4. ペイロードをBase64でエンコード

```sh:ペイロードをBase64でエンコード
echo -n '{"sub":"1234567890","iat":1516239022}' | base64
```

```none:Base64エンコードされたヘッダ
eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ
```

　5. ヘッダとペイロードを `. (ドット)` で結合し、署名なしTokenを生成

```none:署名なしトークン
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ
```

　6. 署名なしTokenに対し、秘密鍵とHMAC-SHA256を用いて署名を生成

```sh:署名なしTokenに、HMAC-SHA256を適応
echo -n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ' | \
openssl dgst -binary -sha256 -hmac 'secret' | \
base64
```

```none:署名
t42p4AHef69Tyyi88U6+p0utZYYrg7mmCGhoAd7Zffs
```

　7. 署名なしTokenと署名を `. (ドット)` で結合

```none:JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
t42p4AHef69Tyyi88U6+p0utZYYrg7mmCGhoAd7Zffs
```

# 実装

JWTの生成と検証を、Javascriptで実装する。

## 前提条件

秘密鍵の値を、`secret`とする

## JWTの生成

```js:JWTの生成
const crypto = require('crypto')

const base64 = json => {
    const jsonStr = JSON.stringify(json)
    const jsonB64 = Buffer.from(jsonStr).toString('base64')
    const jsonB64NoPadding = jsonB64.replace(/={1,2}$/, '')
    return jsonB64NoPadding
}

const HMAC_SHA256 = (key, data) => {
    const hash = crypto.createHmac('sha256', key).update(data).digest('base64')
    const hashNoPadding = hash.replace(/={1,2}$/, '')
    return hashNoPadding
}

const header = { alg: 'HS256', typ: 'JWT' }
const payload = { sub: '1234567890', iat:1516239022 }
const key = 'secret'
const unsignedToken = `${base64(header)}.${base64(payload)}`
const signature = HMAC_SHA256(key, unsignedToken)
const jwt = `${unsignedToken}.${signature}`

console.log(jwt)
```

```none:出力結果
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
t42p4AHef69Tyyi88U6+p0utZYYrg7mmCGhoAd7Zffs
```

- JSONをBase64エンコーディング
[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJKF8gpizJue9GO87WagBClFJ7ZQkVBkbyrZyRA8Sc0b7cb5YSQcRok6d5u-RDZvisFsxUyxYvKuaBkpX4Iq1YIWh8eAP9BsSkUTgtMuaBGmYi4EWqBYw7rBmKe4C1)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJKF8gpizJue9GO87WagBClFJ7ZQkVBkbyrZyRA8Sc0b7cb5YSQcRok6d5u-RDZvisFsxUyxYvKuaBkpX4Iq1YIWh8eAP9BsSkUTgtMuaBGmYi4EWqBYw7rBmKe4C1)

- HMAC-SHA256より、JWTを生成

[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJK7g-TzFJ2RsF6rSzRfmUaf-TcgV4XKKChDJ3rz5PDzZ3qSZK3AHe-OodwzjUzcu5IcB88lCWcXytzpzkRdxSjFrnyzAd3KkKkD158Ce24bDIClFI7pTDUZbpoTE-tKfS648GXLxX8K0v80dc0dt3nUMGcfS2z1i0)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJK7g-TzFJ2RsF6rSzRfmUaf-TcgV4XKKChDJ3rz5PDzZ3qSZK3AHe-OodwzjUzcu5IcB88lCWcXytzpzkRdxSjFrnyzAd3KkKkD158Ce24bDIClFI7pTDUZbpoTE-tKfS648GXLxX8K0v80dc0dt3nUMGcfS2z1i0)

## JWTの検証

```js:JWTの検証
const crypto = require('crypto')

const HMAC_SHA256 = (key, data) => {
    const hash = crypto.createHmac('sha256', key).update(data).digest('base64')
    const hashNoPadding = hash.replace(/={1,2}$/, '')
    return hashNoPadding
}

const key = 'secret'
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.t42p4AHef69Tyyi88U6+p0utZYYrg7mmCGhoAd7Zffs'

const splits = jwt.split('.')
const unsignedToken = [splits[0], splits[1]].join('.')
const signature = splits[2]

console.log(HMAC_SHA256(key, unsignedToken) === signature)
```

```js:出力結果
true
```

[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJKF8A3r7Y0fCAkY3-yxsRdaxenIBqk77LuyRfZvix7ZVlUToq_d7pPg2YvyjdF5tVzh9tArIfXw-ZiswmXwEHgHbG5fT6R8gHUL3vP-kdlDZJ33S7JPJB8JKl1UWL0000)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuUBIqD9KqBLJKF8A3r7Y0fCAkY3-yxsRdaxenIBqk77LuyRfZvix7ZVlUToq_d7pPg2YvyjdF5tVzh9tArIfXw-ZiswmXwEHgHbG5fT6R8gHUL3vP-kdlDZJ33S7JPJB8JKl1UWL0000)

# 認証におけるJWTの利用
新規登録/ログインより、ユーザはJWTを取得する。
APIを利用する際は、JWTを付与してリクエストを行う。
例として、JWTを利用し、ユーザ照会APIにリクエストを行う。 

## 前提条件
- ペイロードは、ユーザIDと有効期限の情報をもつ

```js:ペイロード
{
  "user_id": "1234567890",
  "iat": 1516239022
}
```

- APサーバは、以下のAPIを提供している。

| Method | URL       | Parameter     | Response    | 説明           |
|:-------|:----------|:--------------|:------------|:--------------|
| GET    | /user     | Authorization | ユーザ名を返す | ユーザ情報を返す |

| Method | URL       | Body         | Response   | 説明           |
|:-------|:----------|:-------------|:-----------|:--------------|
| POST   | /register | id, password | Tokenを返す | ユーザ登録を行う |
| POST   | /login    | id, password | Tokenを返す | ログインを行う   |

## 新規登録する場合

1. ユーザ登録
  ① `name=hoge`, `password=hoge`とし、 `/register`にPOSTリクエストする。
  ② APサーバは、DBに対し、新規登録をリクエストする。 
  ③ DBは、ユニークなIDを設定しつつ、`name`, `password`の登録を行う。
  ④ DBは、APサーバに対し、ユーザIDを返す。
  ⑤ APサーバは、ユーザIDと秘密鍵より、Tokenを生成する。
  ⑥ APサーバは、ユーザにTokenを返す。
[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1loWrFREu9BKe9YylCJVLDYC_FJKcho2nkBWyloW5pEL2rW8nmyK9gFNPj0qlblK_dxdxPSyc3DqGdI2520sPAGgsXaR69gPcvXQM1lYqOBeJOitJTJrFThN5v6Mc81c4vZvClI7vsQbvpwVCVzOn0LCj8dXWAXytJNgnVyhZnfbS3gbvAS2m1m00)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1loWrFREu9BKe9YylCJVLDYC_FJKcho2nkBWyloW5pEL2rW8nmyK9gFNPj0qlblK_dxdxPSyc3DqGdI2520sPAGgsXaR69gPcvXQM1lYqOBeJOitJTJrFThN5v6Mc81c4vZvClI7vsQbvpwVCVzOn0LCj8dXWAXytJNgnVyhZnfbS3gbvAS2m1m00)

2. ユーザ情報をリクエスト
  ① `Authorization`にJWTを設定し、 `/user`にGETリクエストする。
  ② APサーバは、秘密鍵を用いてTokenを検証する。
  ③ APサーバは、TokenからユーザIDを取得する。
  ④ APサーバは、DBに対し、IDに対するユーザ情報をリクエストする。
  ⑤ DBは、IDに対するユーザ情報を返す。
  ⑥ APサーバは、ユーザにユーザ情報を返す。
[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1lxGujIWcpx6q92E_ABCgiIGpFyys93yxDOyBMIKshM1LZnlNFLiovyMAFSGgxtxSr1cJbvbYQsXaR69gPcvXQV2uQTBJ_cbFzqt7OeQB4r0ht0VFcbkVBjW8LF-uUTNZflMFcrOyRjhvkBa3pK0ke3PiJiXBp4srpSXFJyLY4TmEe3eKmsBo4DeSKlDIW9410000)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1lxGujIWcpx6q92E_ABCgiIGpFyys93yxDOyBMIKshM1LZnlNFLiovyMAFSGgxtxSr1cJbvbYQsXaR69gPcvXQV2uQTBJ_cbFzqt7OeQB4r0ht0VFcbkVBjW8LF-uUTNZflMFcrOyRjhvkBa3pK0ke3PiJiXBp4srpSXFJyLY4TmEe3eKmsBo4DeSKlDIW9410000)

## ログインする場合

1. ログイン
  ① `name=hoge`, `password=hoge`とし、 `/login`にPOSTリクエストする。
  ② APサーバは、DBに対し、ユーザの確認をリクエストする。 
  ④ DBは、APサーバに対し、対応するユーザIDを返す。
  ⑤ APサーバは、ユーザIDと秘密鍵より、Tokenを生成する。
  ⑥ APサーバは、ユーザにTokenを返す。
[![alt](http://www.plantuml.com/plantuml/svg/IqmkoIzIK7hSlEnnyvx7JTkKk58IInAJ4ejJ5PGS0nutRGK9Dqz05dPnKkBYSYWkIImgAStDuKBIgw1hXwhBIa4_9pyzCoycBoynDzKs8pyzDIQl8B6uk3o_A0NCvK9M3p70nGce3sxeyqMxNgpgnJ00eXce5fkbcIcFcoQzs3_bSUFCcBpC55j38sCJKpDp2qi3DEimjORaPwVcFPyo_rd714or2A_2LCBjuN98pKi11WS0)](http://www.plantuml.com/plantuml/svg/IqmkoIzIK7hSlEnnyvx7JTkKk58IInAJ4ejJ5PGS0nutRGK9Dqz05dPnKkBYSYWkIImgAStDuKBIgw1hXwhBIa4_9pyzCoycBoynDzKs8pyzDIQl8B6uk3o_A0NCvK9M3p70nGce3sxeyqMxNgpgnJ00eXce5fkbcIcFcoQzs3_bSUFCcBpC55j38sCJKpDp2qi3DEimjORaPwVcFPyo_rd714or2A_2LCBjuN98pKi11WS0)

2. ユーザ情報をリクエスト
  ① `Authorization`にJWTを設定し、 `/user`にGETリクエストする。
  ② APサーバは、秘密鍵を用いてTokenを検証する。
  ③ APサーバは、TokenからユーザIDを取得する。
  ④ APサーバは、DBに対し、IDに対するユーザ情報をリクエストする。
  ⑤ DBは、IDに対するユーザ情報を返す。
  ⑥ APサーバは、ユーザにユーザ情報を返す。
[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1lxGujIWcpx6q92E_ABCgiIGpFyys93yxDOyBMIKshM1LZnlNFLiovyMAFSGgxtxSr1cJbvbYQsXaR69gPcvXQV2uQTBJ_cbFzqt7OeQB4r0ht0VFcbkVBjW8LF-uUTNZflMFcrOyRjhvkBa3pK0ke3PiJiXBp4srpSXFJyLY4TmEe3eKmsBo4DeSKlDIW9410000)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9AJ2x9Br9GUjoyx77pdiTDsvIuKX9B4fCIYrCLb1m37ZTj1GatJq0MTd5Iuk92qg6WQuUgraf1lxGujIWcpx6q92E_ABCgiIGpFyys93yxDOyBMIKshM1LZnlNFLiovyMAFSGgxtxSr1cJbvbYQsXaR69gPcvXQV2uQTBJ_cbFzqt7OeQB4r0ht0VFcbkVBjW8LF-uUTNZflMFcrOyRjhvkBa3pK0ke3PiJiXBp4srpSXFJyLY4TmEe3eKmsBo4DeSKlDIW9410000)

# まとめ
JWTを実装しました。実装することで、理解が深まると思います。
また、認証におけるJWTの利用方法についてまとめました。
間違い・指摘等があればコメントお願いします。

# 参考文献
- [公式サイト](https://jwt.io/)
- [JSON Web Token (JWT) - OpenID Foundation Japan](https://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.html)
- [JWTを認証用トークンに使う時に調べたこと](https://christina04.hatenablog.com/entry/2016/06/07/123000)
- [DjangoでJWTを使ったトークン認証を実装する - らっちゃいブログ](http://racchai.hatenablog.com/entry/2016/05/08/070000)
- [Base64デコード(Base64 Decode)｜Web便利ツール@ツールタロウ](https://tool-taro.com/base64_decode/)
- [Generate JWT With Bash - Will Haley](https://willhaley.com/blog/generate-jwt-with-bash/)