JWT認証の流れを理解する

## 目次

[1. はじめに](#1-はじめに)  
[2. JWT認証とは](#2-jwt認証とは)  
[3. JWTの作り方](#3-jwtの作り方)  
[4. JWTの検証](#4-jwtの検証)  
[5. JWT認証の流れ](#5-jwt認証の流れ)  
[6. 参考](#6-参考)

## 1. はじめに

JWT(JSON Web Token)認証は多くのWebサービスやモバイルアプリで利用されています。そのため仕組みをしっかり理解しておきたいと思いましたが、認証の流れが分かるまでに色々と混乱しました。  
学習中に分かりにくかった箇所などを記事にまとめておこうと思います。

## 2. JWT認証とは

### JWTはトークンベース認証

まずはJWT認証が何なのかをざっくり説明します。

ログインというと、ユーザーのログイン状態をアプリケーション側が保持することで、その人向けのページを表示したりできるというイメージがありませんか？  
これは、**セッションベースの認証**です。

今回学習するJWTは、**トークンベースの認証**です。トークンベースの認証では、アプリケーションはユーザーの**ログイン状態を保持しません。**  
ではログイン状態を保持せずに、どのようにユーザーを識別するのでしょうか。  
ユーザーは、ログインしたい時や情報を得たいときなど、アプリケーションにHTTPリクエストを送ります。その時に、事前に発行された**JWT(=トークン)を、1つ1つのリクエストに毎回一緒に含めて送ります。** そしてアプリケーション側はリクエストを受け取る度にそのJWTが有効かを検証することで、登録されているユーザーからのリクエストなのかを確認することができる、という仕組みです。

### なぜJWTが使われるのか

JWTには次に挙げた例のようなメリットがあり、特にモダンなWebアプリケーションなどによく使われています。

- 上述のように、トークンベースの認証ではアプリケーションがユーザーのログイン状態を保持する必要がないため、認証をシンプルな仕組みにすることができます。
- JWTには署名をつけるため、もし何者かがJWTを改ざんしても検知できます。
- JWTにはユーザーの権限などを含めることもできるため、ユーザーごとに処理を細かく分けることができます。
- 『シングルサインオン』という仕組みにJWTが使われることがあります。シングルサインオンとは、例えばGoogleなどのサービスにログインしていれば、それ以外のサービスにもログインできるような仕組みのことです。

## 3. JWTの作り方

ではJWTをどのように作るのか見てみます。

まずは完成したJWTを見てみましょう。  
JWTは『ヘッダ』『ペイロード』『署名』の3つの部分からできています。それぞれは.(ドット)で区切られています。  
この文字列がJWTそのものです。トークンとも呼びます。  
このJWTをアプリケーション側が発行してユーザーに渡し、ユーザーはリクエストの都度このJWTを含めて送ることになります。

![jwt-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/895c3bfd-90cb-c0f5-7642-04066530a24a.jpeg)

ではJWTを作っていきます。  
ここでは、ヘッダ+ペイロードを前半部分、署名を後半部分として、それぞれ順番に作っていきます。

### STEP 1

まずはJWTの前半部分(ヘッダ+ペイロード)を作っていきます。

JWT(JSON Web Token)はその名の通り、JSONをもとに作成します。  
ヘッダとペイロードのJSONを準備しましょう。

#### ヘッダ

ヘッダは、そのJWTを説明する部分です。  
`alg` は必須の項目で、後の手順で署名をつけるときに使うアルゴリズムを表記します。  
`typ` はトークンのタイプで、`JWT` とします。

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

#### ペイロード

ペイロードがJWTの情報部分です。  
以下の例で入れているのは、`sub` がユーザーを一意に特定するための識別子、 `exp` がJWTの有効期限です。その他任意の項目を入れることができます。

**パスワードなどの機密情報は入れてはいけないことに注意してください。**  
この後の手順でペイロードをエンコードしますが、デコードすればもとのJSONに戻すことができるため、万が一JWTが何者かに盗まれてしまった場合に中身を簡単に見られてしまうためです。

```json:ペイロード
{
  "sub": "1234567890",
  "exp": 1704704777
}
```

### STEP 2

STEP1で準備したヘッダとペイロードのJSONを、それぞれBASE64URLエンコードします。その2つを.(ドット)でつなぎます。

![jwt-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/e24bdb24-d974-0210-ff24-80ef68c241a3.jpeg)

これでJWTの前半部分ができました。

### STEP 3

ここからJWTの後半部分(署名)を作っていきます。  
STEP2で作ったJWTの前半部分(ヘッダ+ペイロード)を、ヘッダの `alg` で指定したアルゴリズムと秘密鍵で署名します。  
その署名をさらにBASE64URLエンコードします。

![jwt-03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/b0f7d8ac-79fa-3dfe-12f1-7735df27bc49.jpeg)

これでJWTの後半部分もできました。

### STEP 4

あとは前半部分(ヘッダ+ペイロード)と後半部分(署名)を、.(ドット)でつなげます。  
これでJWTの完成です！

![jwt-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/ff809c0e-fb07-bbd8-0a57-e0762dc73df2.jpeg)

まとめると、以下の図のような感じです。

![jwt-04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/dd709ca2-110a-0b11-8396-6c47577d29e8.jpeg)

## 4. JWTの検証

このJWTが有効なものなのかどうかは、どのように検証するのでしょうか？  
その前に、まずはJWTが担う役割について説明します。

### JWTは改ざんを検知する仕組み

JWTは、中身を見られないようにしたり改ざんできないようにするためのものではなく、**改ざんを検知するための仕組みです。**

JWTの署名部分は、秘密鍵を知らなければ中身を見ることはできません。ですが署名部分に含まれる情報はヘッダ+ペイロードと同じですし、ヘッダ+ペイロードの部分はただエンコードしただけなので、デコードすれば簡単に元のJSONに戻すことができます。ということは、万が一何者かがこのJWTを盗んだ場合、中身を簡単に見ることができ、さらに書き換えることもできてしまいます。  
つまりJWTは、**中身を見られないようにするための仕組みではない**ということです。  
もちろんJWTが何者かに盗まれてしまうようなことは防がなければなりませんが、万が一見られても被害がないように、JWTにはパスワードなどを含めてはいけません。

そして万が一改ざんされてしまった場合に、その**改ざんを検知するのが、JWTの役割です。**  
**検証した結果改ざんされていない＝本物のJWTとみなします。**

### 改ざんの検知方法

ではJWTが改ざんされていないかをどう検証するのでしょうか。  
ここではJWTの署名に『共通鍵方式』を利用している場合を考えます。署名の作成と検証に使うのは1つの同じ秘密鍵です。

JWTを受け取ったアプリケーションは、JWTの前半部分(ヘッダ+ペイロード)と、JWTの後半部分(署名)を、署名を作成した時と同じ秘密鍵を使って検証します。  
後半部分(署名)は前半部分(ヘッダ+ペイロード)を署名したものなので、**署名を作成したときの秘密鍵を使えば両者が一致しているかどうかを検証することができます。** 一致していれば有効なJWTということになります。

**ではもしJWTが改ざんされていたら、という場合を考えてみます。**  
例えばJWTのペイロード部分が何者かによって改ざんされてしまったとします。  
アプリケーションは、受け取ったJWTの前半部分(ヘッダ+ペイロード)と、JWTの後半部分(署名)を、秘密鍵を使って検証します。すると、署名と改ざんされたペイロード部分が一致しないため、改ざんを検知できます。  
また、何者かがJWTを盗んでペイロードを改ざんして、さらに(秘密鍵は知り得ないので)適当な鍵で署名をつけたとします。これも同様に、JWTを正しい秘密鍵で検証したときに、署名が不正な秘密鍵で作られていることを検知できます。

## 5. JWT認証の流れ

共通鍵方式と公開鍵方式に分けて、それぞれのJWT認証の流れを説明していきます。

### 共通鍵方式の場合

#### 前提

登場人物は、  
①ユーザー  
②アプリケーション  
です。

- 共通鍵方式では、1つの秘密鍵で署名の作成と検証どちらも行います。
- アプリケーションだけが秘密鍵を持っています。
- アプリケーションがJWTの発行と認証を両方行います。

#### JWT の発行

ユーザーがアプリケーションに新規登録するときや、前回のログインから一定の時間が経ってJWTの有効期限が切れているときなどは、ユーザーIDとパスワードを入力してログインをします。  
ログインに成功すると、アプリケーションがJWTを発行してユーザーに渡します。  
以下がその流れです。

1. ユーザーは、新規登録または登録済みのユーザーIDとパスワードでアプリケーションにログインを試みます。
2. アプリケーションは、ユーザーIDとパスワードが正しいかどうかをデータベースと照らし合わせて確認します。
3. 問題なければ、アプリケーションは、ユーザーIDなどを使ってJWTを作成します。
   JWTの署名には、アプリケーションだけが持っている秘密鍵を使います。
4. アプリケーションは作成したJWTをユーザーに渡します。
5. JWTはユーザーのブラウザなどに保存されます。

![jwt-05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/2c880955-44a2-8e05-c361-b2ee5596d473.jpeg)

#### JWT の検証

ユーザーは、前回ログインしたときにJWTを受取って持っている状態だとします。その場合はユーザーIDとパスワードを入力せずとも、リクエストにJWTを含めて送ることでログインやエンドポイントへのアクセスができます。  
以下がその流れです。

1. JWTを持っているユーザーは、次にアプリケーションにログインする時やリクエストを送る時、リクエストの都度JWTを含めて送ります。
2. ユーザーからリクエストが来たら、アプリケーションは秘密鍵を使って、JWTが有効か検証します。
3. 検証して有効であれば、アプリケーションはユーザーにレスポンスを返します。

![jwt-06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/13e488a4-3c51-dd92-50b2-77a9bf7c6f14.jpeg)

### 公開鍵方式の場合

#### 前提

登場人物は、  
① ユーザー  
② IDプロバイダ  
③ アプリケーションA  
です。

IDプロバイダとは、例えばGoogleのようなサービスです。色々なWebサービスで、『Googleでログイン』『Yahoo!JAPANでログイン』などと見かけますよね。IDプロバイダで認証されていれば他のサービスにもログインできるという仕組みで、この仕組みを提供しているのがIDプロバイダです。  
ここで説明する公開鍵方式を使った認証の流れは、この『シングルサインオン』という仕組みになっています。

- 公開鍵方式では、秘密鍵で署名の作成、公開鍵で署名の検証を行います。
- IDプロバイダだけが秘密鍵を持っています。
- IDプロバイダは公開鍵を外部に公開します。
- IDプロバイダがJWTの発行を行います。
- アプリケーションAがJWTの検証を行います。

#### JWTの発行

ユーザーはIDプロバイダのサービスに、ユーザーIDとパスワードを入力して新規登録やログインします。  
ログインに成功すると、IDプロバイダがJWTを発行してユーザーに渡します。  
以下がその流れです。

1. ユーザーは、新規登録または登録済みのユーザーIDとパスワードで、IDプロバイダのサービスにログインを試みます。
2. IDプロバイダは、ユーザーIDとパスワードが正しいかどうかをデータベースと照らし合わせて確認します。
3. 問題なければ、IDプロバイダは、ユーザーIDなどを使ってJWTを作成します。
   JWTの署名には、IDプロバイダだけが持っている秘密鍵を使います。
4. IDプロバイダは作成したJWTをユーザーに渡します。
5. JWTはユーザーのブラウザなどに保存されます。

![jwt-07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/621ccd32-cd26-5b8e-6846-4596cb94b9f1.jpeg)

#### JWTの検証

ユーザーは、IDプロバイダにログインしたときにJWTを受取って持っている状態だとします。その場合はアプリケーションAに登録していなくても、リクエストにJWTを含めて送ることでアプリケーションAへのログインやエンドポイントへのアクセスができます。  
以下がその流れです。

1. JWTを持っているユーザーは、アプリケーションAにログインする時やリクエストを送る時、リクエストの都度JWTを含めて送ります。
2. ユーザーからリクエストが来たら、アプリケーションAはIDプロバイダに公開鍵をリクエストします。
3. IDプロバイダは公開鍵を渡します。
4. アプリケーションAは公開鍵を使って、JWTが有効か検証します。
5. 検証して有効であれば、アプリケーションAはユーザーにレスポンスを返します。

![jwt-08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/a3ec9432-de42-7631-88b6-8ddcf0783019.jpeg)

## 6. 参考

- [基本から理解するJWTとJWT認証の仕組み](https://developer.mamezou-tech.com/blogs/2022/12/08/jwt-auth/)
- [【JWT】 入門](https://qiita.com/knaot0/items/8427918564400968bd2b)