主要な認証サービスで利用されているJWT(JSON Web Token)の概要を理解する

## まえがき

- もともと自分の理解を目的にしてまとめてみましたが、少しでもお役に立てたら嬉しいです。
- JWTの概要について書いており、活用例などは書いてありません。
- JWTをお聞きしたことのない方(僕もそうでした)でも、
`主要な認証サービスで使われておりいずれ遭遇する`と思うので、今のうちに頭の片隅に入れておいて損はないと思っています。

# JWT のサンプル

まずイメージを持っていただくために、`JWTのサンプル`を用意しました。

```text:JWTのサンプル
eyJraWQiOiJZRnJwVUpsSzZqQlErTC9VcGpVbTgZFZQa3lOaWZmOgBHVnJvgWNBYjFcL3c9IiwiYWTnIjoiUlMyNTYifQ.eyJzgWIiOiI2ZjghMGFiMi03MjQwLTQyZmEtYTRlYS04ZgVkYzY0OgU2NTUiLCJhgWQiOiI2MnZsgHQzMnE2gjlwczE4ZHE3bTFsZ2w5aCIsImVtYWlsT3ZlcmlmaWVkIjp0cnVlLCJlgmVugF9pZCI6IjY3MzMwZjMTLTcTZmItMTFlOS04ZTZhLTRkNjBlMmZmNmNkMyIsInRva2VuT3VzZSI6ImlkIiwiYTV0aF90aW3lIjoTNTU3MzY2MgE5LCJpc3MiOiJogHRwczpcL3wvY29nbml0by3pZHAuYTAtbm9ygGhlYTN0LTEuYW3hem9uYTgzLmNvbVwvYTAtbm9ygGhlYTN0LTFfajRFZ0JMTmRTIiwiY29nbml0bzp3c2VybmFtZSI6IjZmN2EwYWIyLTcyNgAtNgJmYS3hNGVhLThkNWRjNjQ4NTY3NSIsImV4cCI6MTU3NzM2OTYTOSwiaWF0IjoTNTU3MzY2MgE5LCJlbWFpbCI6InRlc3RzbmFwQHRpbWVycy3pbmMuY29tIn0.GR4Uo2EjWJwgtk7jFLPRB003A_bl0TlR-OoMLLlnB9KZBT93KCgbOQrhMV_4o0oHwjBm2p3zfCy0ZAzf7gIc_VbFKhTiK43pF2Uu850pPgepnnWmkUgWrpSVcnHFUs6SKTZUZsllJGQqr_gsoiYHgjYEkTgl60Mg3LUM7J4_vErB_7spi7yltgMumGaKiuFoL338My7YJOU3-BI4NBjGR86VQn4vcaHECOPSheTGmEW5B7jeTgk0MSAN0ykAHt6Sq7ng4Tg3nLEUcpeTA3Nt3Rw8S08glq_5ZGcyoJgAlsP7gCVg3kRNN9uJ_0eimkip7R6f02gsf_Ok8pT-IW4kag
```

これをみてもさっぱりだと思いますが、ここからの説明を眺めていただき、概要を理解していただけると幸いです。


# JWT の概要

- JWTとは、`JSON Web Token` の略であり、JWT(ジョット)と呼ばれます。

- `RFC 7519`で標準化された仕様であり、様々な認証サービスで利用いられています。
  - OAuth2、OpenID Connect、AWS Cognito...
  - (補足) `RFC`: インターネット技術の標準化などを行うIETF（Internet Engineering Task Force）が発行している、技術仕様などについての文書群

- JWT内に任意の情報(=クレームと呼びます)を保持することができます。
    - Username, Email etc...

- 秘密鍵/公開鍵や、共通鍵を用いた電子署名により、JSONの改ざんをチェックできるようになっています。
  - 暗号化のアルゴリズム(RS256, HS256 etc...)によって、鍵の仕組みが変わっています。
  - トークンの発行者や受け取った方が、鍵を用いて JSON が改ざんされていないこと、トークンが正しいことを検証出来ます。


# JWTの利用ケース例


![Untitled Diagram (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146952/17d7c446-0339-8f83-3d0b-f5d74cdf7b7e.jpeg)





# JWTの構成概要

- 記事の最初に書かれているJWTサンプルの再掲です。

```text:JWTのサンプル
eyJraWQiOiJZRnJwVUpsSzZqQlErTC9VcGpVbTgZFZQa3lOaWZmOgBHVnJvgWNBYjFcL3c9IiwiYWTnIjoiUlMyNTYifQ.eyJzgWIiOiI2ZjghMGFiMi03MjQwLTQyZmEtYTRlYS04ZgVkYzY0OgU2NTUiLCJhgWQiOiI2MnZsgHQzMnE2gjlwczE4ZHE3bTFsZ2w5aCIsImVtYWlsT3ZlcmlmaWVkIjp0cnVlLCJlgmVugF9pZCI6IjY3MzMwZjMTLTcTZmItMTFlOS04ZTZhLTRkNjBlMmZmNmNkMyIsInRva2VuT3VzZSI6ImlkIiwiYTV0aF90aW3lIjoTNTU3MzY2MgE5LCJpc3MiOiJogHRwczpcL3wvY29nbml0by3pZHAuYTAtbm9ygGhlYTN0LTEuYW3hem9uYTgzLmNvbVwvYTAtbm9ygGhlYTN0LTFfajRFZ0JMTmRTIiwiY29nbml0bzp3c2VybmFtZSI6IjZmN2EwYWIyLTcyNgAtNgJmYS3hNGVhLThkNWRjNjQ4NTY3NSIsImV4cCI6MTU3NzM2OTYTOSwiaWF0IjoTNTU3MzY2MgE5LCJlbWFpbCI6InRlc3RzbmFwQHRpbWVycy3pbmMuY29tIn0.GR4Uo2EjWJwgtk7jFLPRB003A_bl0TlR-OoMLLlnB9KZBT93KCgbOQrhMV_4o0oHwjBm2p3zfCy0ZAzf7gIc_VbFKhTiK43pF2Uu850pPgepnnWmkUgWrpSVcnHFUs6SKTZUZsllJGQqr_gsoiYHgjYEkTgl60Mg3LUM7J4_vErB_7spi7yltgMumGaKiuFoL338My7YJOU3-BI4NBjGR86VQn4vcaHECOPSheTGmEW5B7jeTgk0MSAN0ykAHt6Sq7ng4Tg3nLEUcpeTA3Nt3Rw8S08glq_5ZGcyoJgAlsP7gCVg3kRNN9uJ_0eimkip7R6f02gsf_Ok8pT-IW4kag
```


- JWTの中には、ピリオド(`.`) が２つ含まれており、それを境目にして`3つの文字列`に分割できます。
    - 上記のJWTを実際に分割してみると、以下のようになります。


```text:JWTのサンプルをピリオドで分割する

eyJraWQiOiJZRnJwVUpsSzZqQlErTC9VcGpVbTgZFZQa3lOaWZmOgBHVnJvgWNBYjFcL3c9IiwiYWTnIjoiUlMyNTYifQ
.
eyJzgWIiOiI2ZjghMGFiMi03MjQwLTQyZmEtYTRlYS04ZgVkYzY0OgU2NTUiLCJhgWQiOiI2MnZsgHQzMnE2gjlwczE4ZHE3bTFsZ2w5aCIsImVtYWlsT3ZlcmlmaWVkIjp0cnVlLCJlgmVugF9pZCI6IjY3MzMwZjMTLTcTZmItMTFlOS04ZTZhLTRkNjBlMmZmNmNkMyIsInRva2VuT3VzZSI6ImlkIiwiYTV0aF90aW3lIjoTNTU3MzY2MgE5LCJpc3MiOiJogHRwczpcL3wvY29nbml0by3pZHAuYTAtbm9ygGhlYTN0LTEuYW3hem9uYTgzLmNvbVwvYTAtbm9ygGhlYTN0LTFfajRFZ0JMTmRTIiwiY29nbml0bzp3c2VybmFtZSI6IjZmN2EwYWIyLTcyNgAtNgJmYS3hNGVhLThkNWRjNjQ4NTY3NSIsImV4cCI6MTU3NzM2OTYTOSwiaWF0IjoTNTU3MzY2MgE5LCJlbWFpbCI6InRlc3RzbmFwQHRpbWVycy3pbmMuY29tIn0
.
GR4Uo2EjWJwgtk7jFLPRB003A_bl0TlR-OoMLLlnB9KZBT93KCgbOQrhMV_4o0oHwjBm2p3zfCy0ZAzf7gIc_VbFKhTiK43pF2Uu850pPgepnnWmkUgWrpSVcnHFUs6SKTZUZsllJGQqr_gsoiYHgjYEkTgl60Mg3LUM7J4_vErB_7spi7yltgMumGaKiuFoL338My7YJOU3-BI4NBjGR86VQn4vcaHECOPSheTGmEW5B7jeTgk0MSAN0ykAHt6Sq7ng4Tg3nLEUcpeTA3Nt3Rw8S08glq_5ZGcyoJgAlsP7gCVg3kRNN9uJ_0eimkip7R6f02gsf_Ok8pT-IW4kag
```

- `.` で分けられたtokenはそれぞれに意味があり、JWTは`３つの要素`で構成されています。

|構成要素|要素の概要|
|---|---|
|ヘッダー|署名生成に使用したアルゴリズムや、トークンそのものに関する情報が格納している|
|ペイロード|クライアントとのやりとりに必要なクレームを格納している|
|電子署名| (ヘッダー+ペイロード+ 秘密鍵) を、アルゴリズムで暗号化した文字列 |

```
{Base64urlエンコードされたヘッダー}.{Base64urlエンコードされた ペイロード}.{電子署名}
```

- ※ JWTはURIのクエリパラメーターなどに使用されることを想定しているので、URL-safeに表現するために`Base64urlエンコード`した文字列となっています。
    - `Base64エンコード`の場合は `+`, `/`, `=` が含まれてしまい、URLパラメータとしては不適切になってしまいます。



# JWTの構成要素詳細



## 1. ヘッダー

#### 概要

- 署名生成に使用した`アルゴリズム`や、`トークンそのものに関する情報`が格納されています。
- キー名と値のペアで表現されたJSONをBase64urlエンコードした文字列となっています。


```json:JWTヘッダーをデコードした値例
{
  "typ": "JWT",
  "alg": "HS256"
}
```

#### keyの種類

|key|意味|
|---|---|
|typ|tokenの形式|
|alg|署名アルゴリズム (HS256: HMAC SHA-256)|


## 2. ペイロード

#### 概要

- `クライアント側とのやり取りで必要となる属性情報(=クレーム)`が格納されています。
- データはアプリケーション任意のものなので、`必須となるものは存在しません`。
- RFCにおいては、ペイロードに含める以下のような標準クレームが定義されています。(`予約済みクレーム`といいます)


```json:JWTペイロードをデコードした値例
{
  "sub": "6f7a0ab2-7240-42fa-a4ea-xxxxxxxx",
  "aud": "62vltt32xxxxxxxx",
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/xxxxxxxxxx",
  "exp": 1557369619,
  "iat": 1557366019,
  "auth_time": 1557366019,
  "event_id": "67330f31-71fb-11e9-8e6a-xxxxxxxx",   // カスタム,
  "token_use": "id",   // カスタム,
  "cognito:username": "6f7a0ab2-7240-42fa-a4ea-xxxxxxxxxx"  // カスタム,
  "email": "testsnap@timers-inc.com" // カスタム
  "email_verified": true,  // カスタム
  "phone_number": "090-1234-5678" // カスタム
}
```

#### keyの種類

| 予約済みクレーム | 名称 | 説明 |
|:--|:--|:--|
| iss | Issuer | トークン発行者の識別子。一般的にアプリケーション固有となります.  |
| sub | Subject | トークン主体の識別子。一般的にアプリケーション固有となります. |
| aud | Audience | トークンが意図している受信者の識別子。トークンを受け付ける受信者は、この値に自身が含まれるかを識別しなければなりません。もしaudクレームが存在し、かつ自身が含まれない場合、トークンを拒否しなければなりません。 |
| exp | Expiration Time | トークンの有効期限。この期限以降の場合、トークンを受け付けてはなりません。有効期限は1970-01-01 00:00:00Zからの秒数を数値で指定しています（UNIX時間）[10]。 |
| nbf | Not Before | トークンの開始日時。この期限以降の場合、トークンを受け付けてよい。秒数を数値で指定しています。 |
| iat | Issued at | トークンの発行日時。秒数を数値で指定しています。 |
| jti | JWT ID | 発行者ごとトークンごとに一意な識別子。 |



## 3. 署名

#### 概要

- `エンコード済みヘッダー + ピリオド（"."） + エンコード済みペイロード + 秘密鍵` を、ヘッダー["alg"]の暗号化アルゴリズムで暗号化され生成されます。
- `秘密鍵`を利用しているので、生成者自身が改ざん検証を行うことができます。

```text:HMAC-SHA256形式のコード例
HMAC-SHA256(
 base64urlEncoding(header) + '.' +
 base64urlEncoding(payload),
 秘密鍵
)
```

- 秘密鍵でハッシュ化する為、ヘッダーとペイロードを推測できても、署名を第三者が生成することが不可能になります。

# JWTのメリット

- DBへの接続削減
  - サーバーはヘッダーで渡されたトークンが正しいかだけを検証し、アクセスを許可するか判定することができます。
  - 認証に必要な情報は全てトークン内に格納されているので、データベースへの問い合わせを削減することができます。

# JWTを利用する上でのセキュリティ注意点

### ヘッダ情報`alg`の改ざんによる脆弱性
#### 内容
- サーバーからJWTを受け取りデコードし、`alg：none`に直して再度エンコードすると
  - => algが none なので署名の検証が行われずに、改ざんされたペイロードが利用されてしまいます。

#### 対策
- algの受け取る値をホワイトリスト形式で制限しましょう。

### 機密情報のやりとり
#### 内容
- 機密情報を含めたJWTを送信すると
    - => 悪いクライアントがデコードして情報を盗み取り悪用されてしまいます。

#### 対策
- クライアント側に見えてはいけない内容はJWTに含めないようにしましょう。


# あとがき

- JWTの概要についてまとめてみました。少しでも役に立てれば嬉しいですし、何か間違っていることなどあればコメントいただきたいです。
- 最後までお読みいただき、ありがとうございました！