WebアプリやAPIの認証について調べていると、「JWT」という言葉を目にすることがあります。
JWTはWebサービスの認証などでよく利用されていますが、初めて見ると、
- JWTって何?
- 普通のログインと何が違うの?
- JWTの中には何が入っているの?
と、少し分かりにくく感じるかもしれません。
この記事では、そもそもJWTとはなにか・JWT認証の仕組み・JWTの作り方・JWT認証の流れについて、初心者向けにわかりやすく解説します。
JWTとは?
JWTとは、JSON Web Token(ジェイソン・ウェブ・トークン)の略です。JWTの読み方は「ジョット」です。
簡単にいうと、次のようなものです。
ユーザーIDなどの情報を含め、それが途中で改ざんされていないことを確認できるトークン
実際のJWTは、次のような文字列になっています。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9.--PQ_CxidLm4eUYKBFnz1tOGwbKAMRdFVd_B1O8s650かなり長い文字列ですが、よく見ると「.(ドット)」で3つに区切られており、次の3つの部分からできています。
Header.Payload.Signature
- Header(ヘッダー)
- 署名に使用するアルゴリズムなどの情報が入っている
- Payload(ペイロード)
- ユーザーIDや有効期限などの情報が入っている
- Signature(署名)
- JWTが途中で改ざんされていないことを確認するための情報が入っている
なお、JWTそのものは「認証方式」ではありません。JWTはあくまでトークンの形式であり、このJWTをログイン後のユーザー確認などに利用する仕組みが「JWT認証」です。
なぜJWTが使われるのか
では、なぜJWTはWebアプリやAPIの認証でよく使われるのでしょうか。
主な理由をこれから説明します。
サーバー側でログイン状態を保持しない構成にできる
従来からよく使われているセッション認証では、一般的にサーバー側で「このユーザーはログイン済み」という情報をセッションとして管理します。
一方、JWTを使った認証では、クライアントがJWTをリクエストと一緒に送り、サーバーはそのJWTを検証します。そのため、サーバー側でユーザーごとのセッション情報を保存しない構成にすることができます。
例えば、複数のAPIサーバーでリクエストを処理するシステムでも、各サーバーがJWTを検証できれば認証処理を行えます。
JWTが改ざんされていないか確認できる
Webサービスの認証で使われるJWTでは、一般的にSignature(署名)が付けられています。
サーバーはこの署名を検証することで、JWTのHeaderやPayloadが途中で変更されていないか確認できます。
例えば、Payloadに次の情報が入っていたとします。
{
"admin": false
}悪意のあるユーザーがこれを、
{
"admin": true
}に書き換えたとしても、元のSignatureとは一致しなくなります。
そのため、サーバーが署名を正しく検証すれば「このJWTは途中で改ざんされている」と判断できます。
ユーザーに関する情報を含められる
JWTのPayloadには、ユーザーIDだけでなく、ユーザーの権限などの情報を含めることもできます。
例えば次のような情報です。
{
"sub": "123",
"role": "admin"
}サーバーはJWTを検証したうえで、このroleなどの情報を利用して、
- 一般ユーザーが利用できる処理
- 管理者だけが利用できる処理
といったように、ユーザーの権限に応じて処理を分けることもできます。
なお、Payloadの内容は暗号化されているわけではないため、パスワードなどの秘密情報を入れてはいけません。
複数のサービス間でも利用しやすい
JWTは、複数のサービスでユーザー情報などを受け渡す用途にも利用できます。
例えば、以下のような使い方をすることができます。
- 認証を担当するサービスがJWTを発行する
- 別のAPIサーバーがそのJWTを受け取る
- APIサーバーが署名を検証する
- 正しいJWTであれば処理を行う
JWT認証とは?
JWT認証とは、JWTを利用してユーザーからのリクエストを確認するトークンベースの認証方式です。
まず、一般的なWebサービスのログインを考えてみましょう。従来からよく使われているのが「セッション認証」です。
セッション認証では、ユーザーがログインすると、サーバー側で「このユーザーはログイン済み」という情報を保持します。
そのため、ユーザーが別のページを開いたときでも、サーバー側に保存されているログイン情報を確認することで、そのユーザーがログイン済みであることを判断できます。
一方、JWT認証では、サーバー側にセッションとしてログイン状態を保存せず、ユーザーが持っているJWTを利用して認証します。
ユーザーが最初にIDやパスワードなどを使ってログインに成功すると、サーバーがJWTを発行します。
その後、ユーザーはAPIなどへリクエストを送るときに、発行されたJWTをリクエストと一緒に毎回送ります。
一般的には、HTTPリクエストのAuthorizationヘッダーに次のような形でJWTを設定します。
Authorization: Bearer <JWT>サーバーはリクエストを受け取るたびにJWTを検証し、
- 署名が正しいか
- 有効期限が切れていないか
- 必要な情報が正しいか
などを確認します。問題がなければ、「これは正しく発行されたJWTを持っているユーザーからのリクエストだ」と判断でき、JWTに含まれているユーザーIDなどを利用して処理を行います。
つまりJWT認証は、最初にIDやパスワードなどでログインし、その後は発行されたJWTをリクエストと一緒に送るという仕組みです。
JWTの中身・作り方
それでは、JWTがどのように作られているのか見ていきましょう。
先ほど説明したように、JWTは次の3つの部分から構成されています。
Header.Payload.Signature
ここでは、一例として、署名方式としてHS256を使用すたJWTを作ってみます。
STEP1:HeaderとPayloadを用意する
まず、HeaderとPayloadのJSONを用意します。
Header(ヘッダー)
Headerには、JWTに関する基本的な情報を入れます。
今回は次のJSONを使用します。
{
"alg": "HS256",
"typ": "JWT"
}algはJWTの署名に使用するアルゴリズムを表します(algは必須の項目です)。今回指定しているHS256はHMAC-SHA256を利用する方式です。
typはトークンの種類を表しており、JWTの場合はJWTとなります。
つまり、このHeaderは簡単にいうと、「このトークンはJWTで、HS256を使って署名されています」という情報が入っています。
Payload(ペイロード)
Payloadには、ユーザーなどに関する情報を入れます。
今回は次のJSONを使用します。
{
"sub": "1234567890",
"name": "Taro",
"admin": false
}それぞれ次のような意味です。
sub:ユーザーを識別するIDname:ユーザー名admin:管理者かどうか
実際のシステムでは、これ以外にも有効期限を表すexpなどを含めることがあります。
ここで非常に重要なのが、Payloadにはパスワードなどの秘密情報を入れてはいけないという点です。
このあとPayloadをBase64URLエンコードしますが、これは暗号化ではありません。デコードすれば簡単に元のJSONを確認できるからです。
STEP2:HeaderとPayloadをBase64URLエンコードする
次に、先ほど作ったHeaderとPayloadをそれぞれBase64URLエンコードします。
例えばHeaderをBase64URLエンコードすると、次のような文字列になります。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Payloadも同じようにBase64URLエンコードします。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9そして、この2つを「.(ドット)」でつなげます。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9これで、署名を作るためのHeader.Payloadが完成しました。
STEP3:Signature(署名)を作る
最後にSignatureを作ります。
Signatureは、JWTのHeaderやPayloadが途中で改ざんされていないことを確認するために利用します。
ヘッダの algで指定したHS256(HMAC-SHA256)を使い、Header.Payloadと秘密鍵からSignatureを作ります。
今回は説明用として、次の秘密鍵を使用します。
my-secret-keyそして、先ほど作ったHeader.Payloadに対して、秘密鍵(my-secret-key)を使ってHMAC-SHA256による計算を行います。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9計算結果をBase64URLエンコードすると、今回のSignatureは次のようになります。
--PQ_CxidLm4eUYKBFnz1tOGwbKAMRdFVd_B1O8s650これでJWTの3つ目の部分であるSignatureが完成しました。
なお、実際のシステムでは「my-secret-key」のような簡単な秘密鍵を使用してはいけません。あくまで仕組みを理解するための例として簡単な秘密鍵を使用しています。
STEP4:Header・Payload・Signatureをつなげる
最後に、作成した3つの文字列を「.(ドット)」でつなげます。
Headerは次の文字列です。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Payloadは次の文字列です。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9Signatureは次の文字列です。
--PQ_CxidLm4eUYKBFnz1tOGwbKAMRdFVd_B1O8s650これらを、
Header.Payload.Signature
の順番でつなげます。
すると、次のJWTが完成します。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRhcm8iLCJhZG1pbiI6ZmFsc2V9.--PQ_CxidLm4eUYKBFnz1tOGwbKAMRdFVd_B1O8s650これが実際のJWTです。
ここまでの作り方をまとめると、以下の流れになります。
HeaderのJSONを作るPayloadのJSONを作るHeaderとPayloadをBase64URLエンコードするHeader.Payloadに対して署名を作り、Signatureを生成するHeader.Payload.Signatureの順番でつなげる
JWT認証の流れ
最後に、JWT認証が実際にどのような流れで行われるのか見てみましょう。
まず、ユーザーがWebサービスへログインします。
ユーザーは、ユーザーIDやメールアドレス、パスワードなどをサーバーへ送信します。
サーバーはデータベースに保存されているユーザー情報を確認し、ログイン情報が正しいかをチェックします。
ログインに成功すると、サーバーはそのユーザー用のJWTを作成します。JWTのPayloadには、ログインしたユーザーを識別するためのIDや有効期限などを含めることができます。
そして、サーバーは作成したJWTをユーザーへ返します。
ユーザー側は受け取ったJWTを保持しておきます。
その後、例えば「自分のプロフィール情報を取得する」といった、ログインが必要なAPIを利用するとします。このとき、ユーザーは先ほど発行されたJWTをリクエストと一緒に送ります。一般的には、次のようにAuthorizationヘッダーへJWTを設定します。
Authorization: Bearer <JWT>リクエストを受け取ったサーバーは、JWTを検証します。具体的には、次のような点を確認します。
- JWTが改ざんされていないか
- 有効期限が切れていないか
JWTに問題がなければ、サーバーはPayloadに含まれているユーザーIDなどから、どのユーザーから送られたリクエストなのかを判断できます。そして、そのユーザーに許可されているAPIの処理を実行します。例えば、プロフィール取得APIであれば、そのユーザーのプロフィール情報を返します。
その後も、ログインが必要なAPIへアクセスするたびにJWTを送ります。
サーバーは毎回JWTを検証することで、ユーザーを認証します。
つまりJWT認証は、最初にIDやパスワードでログインし、JWTを発行してもらい、その後はJWTを使って認証する仕組みと考えると分かりやすいでしょう。
本記事のまとめ
この記事では『JWT』について、以下の内容を説明しました。
- JWTとは何か
- JWTが使われる理由
- JWT認証の仕組み
- JWTの中身と作り方
- JWT認証の流れ
お読みいただきありがとうございました。