JWT(JSON Web Token)って何に使うの？仕組みとその利便性

#JWTとは
タイトルにもある通り、
`JSON Web Token`の略で
読み方は、「ジョット」です。

なんか音的に、ジェット機を思い浮かべてしまうのは、私だけでしょうか、、、？
なにかものすごく空を飛ぶ印象を受けてしまいます。なので、やりとりが早そうなイメージがまとわりつきますねえ、これは。
ただただ、便利そう◎

`JSON`の本名が、`JavaScript Object Notation`なので、

実際は、
`JavaScript Object Notation Web Token`
ということになるんでしょうかね。

ちなみに、JWTの仕様は[RFC7519（外部サイト）](https://tools.ietf.org/html/rfc7519)で定義されており、
**属性情報（Claim:クレーム）をJSONデータ構造で表現したトークンの仕様**
※文句のクレームではありませんw

JWTの署名`JSON Web Signature（JWS）`、暗号化`JSON Web Encryption（JWE）`にまつわる仕様も、
それぞれ[RFC7515（外部サイト）](https://tools.ietf.org/html/rfc7515)と[RFC7516（外部サイト）](https://tools.ietf.org/html/rfc7516)に公開されています。

####RFCとは
>RFC（Request for Comments）は、インターネットで用いられるさまざまな技術の標準化や運用に関する事項など幅広い情報共有を行うために公開される文書シリーズです。
引用元：[日本ネットワークインフォーメーションセンター](https://www.nic.ad.jp/ja/newsletter/No24/090.html)

#そもそもトークンとは、ﾅﾝｿﾞﾔ
>トークン (英語: token) ,「しるし」「象徴」、「記念品」「証拠品」の意。
引用:[wikipedia](https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3)

ふむふむ、何かを証明するための印みたいなイメージでしょうかね

英語の類義語としては、
`symbol, sign, emblem, mark, badge`
などになるので、

つまり、感覚的にいうと、「**〇〇証**」っていうのがしっくりくるような。

例えば、

- 許可証
- 通行証証
- 身分証

などなど。
つまり、`JWT`っていうのは、**Webアプリケーション上で、「〇〇証」として使う、何か**　ってことになりますねえ

#JWTの構造と仕組み
実際に、JWTの実態をみてみましょう〜
JWTのデバッグができる**[jwt.io](https://jwt.io/)**で生成した`JWT`そのものを貼り付けてみます。

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9.RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
```
ただただ、意味不明な文字列が、ダァぁぁああっと横に伸びていますが、、、
しかも横スクロールしないと見えないし、、、、
これだとなんなのか分からないですね。


以下が、作成した時のスクショです。
こちらは**<font color="DarkViolet">色が付いていて、視覚的に美しい！</font>**
![スクリーンショット 2021-02-04 19.04.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/0f692d66-a669-a497-8b78-027cd692fb77.png)

何やら3構造になっている。
そして、`.`で色が区切られているではないかぁああ！！！！

つまり、こういうことか、

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9
.
RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
```
```
<ヘッダ>.<ペイロード>.<署名>
```

上述の、横長ベタ貼りのやつは、絶対に`.`なんて探すの無理。
まじで、無理。マジで、ウォーリーを探せ状態。
常人には気づけなんて、無理ですね、はい。orz

##JWTは3構造から成っている

###ヘッダー
ヘッダーでは、**署名で使うハッシュ値を得る`アルゴリズム`と`トークンタイプ(データ型)`を指定**します。

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```
####<解説>
`alg`：署名のハッシュ値を出力するアルゴリズムは、`HMAC SHA-256`だよ
`typ`:そのオブジェクトが`JWT`だよってことを示す

###ペイロード
ペイロードでは、**実際に取り扱いたい実際のデータを指定**します。
クレームデータとも呼ばれ、自分で**任意のプロパティを含ませる**こともできます。
例えば、

```json
{
  "iss": "ichi_zamurai",
  "sub": "what_is_jwt?",
  "name": "ichi_zamurai",
  "admin": true,
  "exp": 1614524400,
  "iat": 1516239022
}
```
####<解説>
※`name, admin`なんかは任意でいれたプロパティ
※`exp`：`2021-03-01 00:00:00`まで有効期限を設定してみました
[Unixtime相互変換ツール](https://tool.konisimple.net/date/unixtime)なんかを使うと便利です。


**予約語もあって**、それぞれの意味はこちらです。
すべて、指定するかどうかは**任意(optional)**、つまり自由だぁぁぁぁあああ。

| 予約語 | 意味 | 説明 | 指定データ型 |
|:-:|:--|:--|:--|
| iss | Issuer | JWT発行者(サーバー側)の識別子 |文字列 / StringOrURI|
| sub | Subject | JWTの主語となる主体の識別子 |文字列 / StringOrURI|
| aud | Audience | JWT を利用する主体(クライアント側)の識別子 |文字列/StringOrURI 値の配列<br>※単一も可能|
| exp | Expiration Time | 有効期限日時。期限以降にこのJWTの処理はNG|1970-01-01 00:00:00Zからの秒数を<br> 数値(IntDate)で指定|
| nbf | Not Before | 有効期間開始日時。これ以前にこのJWTの処理はNG。 |1970-01-01 00:00:00Zからの秒数を<br> 数値(IntDate)で指定|
| iat | Issued At | JWTの発行日時。 |1970-01-01 00:00:00Zからの秒数を<br> 数値(IntDate)で指定|
| jti | JWT ID | JWTのための一意(ユニーク)な識別子。<br>重複が起きないように割り当てる必要がある。 |大文字と小文字を区別する文字列|
| typ | Type | コンテンツタイプの宣言 |大文字と小文字を区別する文字列|


###シグニチャー
シグニチャーでは、**署名**を表します。
翻訳したまんまですね、これじゃまるでわからないのでもう少し解説を。

```
ヘッダーのオブジェクトを`Base64Url`を使ってエンコード生成した文字列
+
ドット
+
ペイロードのオブジェクトを`Base64Url`を使ってエンコード生成した文字列
,
ソルト(シークレットスパイスを少々)
```
これを**`HS256`のアルゴリズムで、ハッシュ化して生成されたダイジェスト（文字列）が署名**になります。

うーん、なんだかいまいち分からん。

上記の言葉をコード的にまとめると、以下になります。
※以下、なんの言語でもないです、ただのイメージ

```
data = base64urlEncode( header ) + '.' + base64urlEncode( payload )
signature = Hash( data, secret );
```
結構簡潔w
つまり、

```
#ヘッダー
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
#ドット
.
#ペイロード
eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9
#秘密キー
hogehogehoge
```
で生成した署名の文字列が、上記のキャプチャにもあるこれ

```
RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
```


####何故、`base64urlEncode`を使うのかというと

それは、形式的にいうと、`URL-safe`（URL的に優しい）にするためです。

`JWT`は、基本、URIのクエリパラメータとして使用されることを想定していて、
Base64UrlEncodeは、**`'+'→'-'`、`'/'→'_'`、`'='→''`に変換してくれる**

というのも、`+` `/` `=`は**予約文字**として、確保されてしまっているので、
その文字列がURLに入ってきてしまうと、特別な能力を発揮してしまうため、
**無効化する必要がある**んですねえ。

つまり、**`URL-safe`にするために、`Base64UrlEncode`を使っている**ということだね！



#結局、JWTって何に使うの？
単刀直入に、**ユーザー認証**に使えます。
何かしらのサービスにログインしたとき、その認証情報をもっておく必要があるが、
その際のユーザー名やその有効期限などを `JWT` として保持しておくといった感じです。

エンジニア界隈では、
JWTは危ないやら、セッションに使うべきではないやら、
色々と議論が沸き起こっているみたいですが、
確実に言えることは、認証、それです。

認証の方法として、独自でも作れるみたいですが、
`OAuth 2.0`やら、`OpenID Connect`やらがあって、
そこでもJWTは活躍してるそうです。
複雑になりすぎので、ここでの説明は割愛します。



#JWTの利便性（メリット）
主に、`安全性`、`実装のシンプルさ`、`管理の容易さ`が特徴です

箇条書きで列挙します〜

- 署名が含まれているため、**改ざんがあってもチェックできる**ようになっていて、一応安全
- 安全なトークンの発行が比較的簡単に実装できる
- 認証確認の際に、**署名の検証処理だけ**で済む
- サーバ側で、ユーザーの認証状態の管理が必要ない（**ステートレスに**）
 - Cookie認証だと、DBにSession情報を保持する必要がある。
- 認証に**DB問い合わせが必要ない**
- ペイロードに任意のデータを詰められて**スケーラブル**
 - Cookieは4KBで保持できるデータ量が少ない
- Cookieヘッダを使わない場合に、**`CORS(Cross-Origin Resource Sharing)`の制約も乗り越えられる**
- JSONをデコードすれば、**フロントエンドからも直接データにアクセス可能**
- **Cookieを使用しないプラットフォーム（モバイルとか）でもJWTが使え**、IoTとかの分野でも認証で活躍できるらしい
- URLのパラメータに含める文字列だから、**HTTPリクエストでの取り扱いがシンプル**



#JWTによる認証の一例

ざっくりとしたJWTを使った認証の流れは、

`ヘッダーorペイロードが改ざんされてないかチェック`
→ `ペイロードの有効期限(exp)が切れていないかチェック`
→ `問題がなければ認証OK`

こんな感じですねえ！

###ログイン以前

1. フロントエンドからユーザー情報（ID、パスワード等）をリクエスト
2. バックエンドで受け取ったIDとパスワードの検証
3. OKなら、秘密鍵でJWT発行（※認証されたユーザー情報と有効期限を含める）
4. 発行したJWTをHTTPレスポンスヘッダーのCookieに詰めてフロントエンドに返す
5. フロントエンドでbase64で変換し、LocalStorageに保存
6. ログインは完了

###ログイン以降
認証されたユーザーがブラウザで、AmazonなどのECサイトで商品購入など、認証が必要な操作をした場合に、

1. フロントエンドでLocalStorageにトークンがあるか確認 
    - ある場合は、その有効期限を確認し、HTTPヘッダに入れてバックエンドにリクエスト。
        - 切れている場合は、バックエンドに問い合わせし、バックエンドでトークンを更新（リフレッシュ）して返す
            - フロントエンドでは、初回登録時と同様に返ってきたトークンをLocalStorageに保存
    - ない場合は、ユーザーの種類によって適切なページへリダイレクト
2. バックエンドで秘密鍵を使って、受け取ったJWTが改ざんされていないかチェック
3. バックエンドでJWTの中身を確認して有効期限が切れていないかチェック
4. バックエンドでOKならそのまま処理を継続、必要に応じてフロントエンドにデータを返す。
5. バックエンドでNGなら、認証エラーをフロントエンドにレスポンス


#JWT認証の注意事項

JWTのヘッダーとペイロードはエンコードされただけのデータなので、
**誰でもデコードすればその中身が見えてしまう**という事実があります。
盲点ですなあ

なので、**改ざんされるリスク**があって、
「でも署名を使ってるから大丈夫だろ〜」って　安心しちゃうじゃないですか？！

そこに落とし穴があって、

```json
{
  "alg": "none",
  "typ": "JWT"
}
```

`"alg": "none"`とされてしまったときに、
ライブラリによっては、**署名のチェックを素通りしてしまうと言う現象**が起こります。

ここが盲点となって、
いろいろあんな恐ろしいことやこんな残酷なことをクラッカーたちは攻撃してきますので、
覚えておきましょう。

ライブラリによっては、`"alg": "none"`の認証は失敗させるというような処理に対応してきたそうです。
（※具体的にどれがとかは、ちょっとまだ未調査です、すみませぬ。。。）


#まとめ

###JWTは、便利ですねえええ！！！！！！！！！！！！

認証周りは深いいですねぇ

以上、

ありがとうございました！！