JWTの基本とJWSを使った認証を理解する

# この記事を書こうと思った経緯
仕事で携わっているプロダクトの認証機能にJWTを使用しているのですが
「認証の時に使う暗号化された文字列」
ぐらいにしかわかっていなかっていませんでした。


JWT、そして認証で使うJWSとJWTについてまとめてみました。


# JWTを簡単にまとめると？
JWTは`Json Web Token`を略したものになります。

RFC（IETFという「インターネット関連の基準を決めている組織」によって取りまとめた基準を文書にしたもの）によると、JWTとは
> HTTP AuthorizationヘッダーやURIクエリパラメーターなど、スペースに制約のある環境向けのコンパクトなクレーム表現形式です。

とあります。

「クレーム表現形式」というのはJWTの用語で、JSONのキーと値のペアのことです。
ここでは「JSONオブジェクト」と考えてください。

つまり、「スペースに制約のある環境向けのコンパクトなJSONオブジェクトの表現形式」ということです。

ちなみに、JWTの読み方は`ジェイダブリューティー`ではなく、`ジョット`ですので覚えておきましょう。



# JWTの形式

JWTを構成する形式は大きく2つです。

1. JSONオブジェクトをURLに乗せられるようにエンコードする
2. コンパクトになるように短いキー名が用意されている

...........これだけ??

と思ったあなた、

そうです、JWT自体はこれだけです。


## 1. URLに乗せられるようにエンコードする

URLには`/`や`+`といった特定の文字は使えません。
そこで、`Base64URLエンコード`を行い、URLに使える文字に変換します。


元のJSONオブジェクト
```JSON
{
  "sub": "1234567890",
  "iat": 1516239022,
  "email": "hogehoge+hugahuga@example.jp"
}

```
↓　
Base64URLエンコードした文字列
```
eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJlbWFpbCI6ImhvZ2Vob2dlK2h1Z2FodWdhQGV4YW1wbGUuanAifQ
```
`+`という文字がエンコードされ、URLに使える文字列になりました。


## 2. コンパクトになるように短いキー名が用意されている

JWTにはJSONオブジェクトのキーが短く表せるよう省略形が定義されています。

|キー|意味|日本語|
|:-:|:-:|:-:|
|iss|Issuer|JWTの発行者|
|sub|Subject|ユーザの識別子などJWTの主体|
|aud|audience|JWTの受信者|
|exp|expiration time|JWTの有効期限|
|nbf|Not Before|JWTの有効開始日時|
|iat|Issued At|JWTの発行日時|
|jti|JWT ID|JWTの一意な識別子|


実際に使うときには、このような表示形式になります。
```JSON
{
  "iss": "io.exact.sample.jwt",
  "sub": "sample",
  "exp": 1670085336,
}
```




あれ、、、認証とか暗号化とかの話は出てこないの。。。。。？と思った方、その通りです。

JWT自体は認証や暗号化をしているわけではなく、あくまで表示形式のことなのです。

じゃあ認証に使っていたJWTって、一体なんだったの.....?
これについて述べていきたいと思います。

# JWS

先ほどお伝えした通り、JWT自身には認証や暗号化はされていません。
Base64URLでエンコードされたものはデコードすれば元のJSONを知ることができます。
JWTではセキュリティに関しては考慮されていません。

そこで、JWTを使って認証するには **JWS** というものが使われます。

JWSとは **JSON Web Signature** の略で、「データの改竄（かいざん）を検知」することができます。

ここで注意するのは
改竄を**できないようにする**のではなく、
改竄したということを**知ることができる**ということです。


「送信元が意図したとおりの形でデータが受け取り側に到達したかを確認する」手段としてデータ署名を使います。


ここからは[公式サイトの画面](https://jwt.io/)を使って説明していきます。
![スクリーンショット 2023-06-18 23.08.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320921/fca4bf86-6f19-46db-fe3e-07033513e318.png)




左側の**Encoded**のところに書かれた長い文字列が、エンコードされたJWS
右側の**Decoded**のところに書かれているのがJWT形式で書かれたJSONオブジェクトになります。

これを見て分かる通り、JWSは
- **Header（ヘッダー）**
    - 署名に利用された鍵やアルゴリズム名などの情報
- **Payload（ペイロード）**
    - 本文
    - ここにJWTが入っている
- **Verify Signature（シグニチャー）**
    - 本文から作成した署名

の3部分から構成されており
**それぞれをBase64URLでエンコード**し、**`.`（ドット）で繋いだもの**
となっています。




ここで、「クレーム」に関連する用語について深掘りしていきましょう


クレームとは、RFCによると
> 対象について主張された情報の一部。クレームは、クレーム名とクレーム値で構成される名前と値のペアとして表されます。

と定められており、画像での
`"iss": "sample.jwt"`や`"sub": "sample"`はそれぞれクレームと呼ばれるということになります。

また
`"iss"`や`"sub"`をクレーム名、
`"sample.jwt"`や`"sample"`をクレーム値と呼びます。

さらに
クレームセットとというのは、以下のようにJWTによって伝えられたクレームを含むJSONオブジェクトを指します。
```JSON
{
  "iss": "sample.jwt",
  "sub": "sample",
  "exp": 1670085336,
  "email": "sample@example.com"
}
```




....
さて、ペイロードのお話に戻すと、ペイロードにはJWTのクレームセットが入っています。


# JWSの生成方法
生成手順は下記のとおりになります。
生成した1つの文字列となりますが、この文字列を生成する手順を「JWSコンパクトシリアライゼーション」と呼びます。


1. ペイロードをBASE64URLエンコードする
1. ヘッダーをBASE64URLエンコードする
1. BASE64URL エンコードした1と2の結果を.(ドット)で繋ぐ
1. 3の結果を暗号鍵とalgに指定された方式で署名(暗号化)し、その結果をさらにBASE64URLエンコードする
1. 3(ヘッダー+ペイロード)と4(シグニチャ)の結果を.(ドット)で繋ぐ


つまり、3で生成されたものと、4で生成されたものを複合化した時に一致していないとおかしいということになり、異なる場合には改竄されたということになります。


# JWSの検証方法
共通鍵方式（秘密鍵方式）を使った場合で考えていきます。

先ほどお伝えした通り、シグニチャーは復号化したものは、ヘッダーとペイロードを`.`（ドット）で繋いだものと全く同じになるはずです。
![JWTの検証.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320921/127090ce-9004-66f8-eb89-752993aeb191.png)



# まとめ
いかがだったでしょうか。

JWTは「HTTP AuthorizationヘッダーやURIクエリパラメーターなど、スペースに制約のある環境向けのコンパクトなクレーム表現形式」

JWSは「JWTをJWSコンパクトシリアライゼーションしたもの」

以下を押さえておけば混乱しないかと思います。



JWTに関する
JWE（Json Web Encrryption）やJWK（Json Web Key）などもあるので
そちらはまた今度まとめたいと思います。


# 参考文献
https://tex2e.github.io/rfc-translater/html/rfc7519.html

https://jwt.io/

https://developer.mamezou-tech.com/blogs/2022/12/08/jwt-auth/#jwt%E3%81%A8%E3%81%AF

https://www.amazon.co.jp/o/ASIN/4297134772/gihyojp-22