ラボ: JWT入門
Published on May 20, 2024はじめに
このラボでは、以下の内容について学びます。
- JSON Web Token(JWT)の概要
- JWTの構造
- Base64でエンコードされた文字列のデコード方法
- JWTにおける署名の検証方法
JSON Web Token (JWT) とは
JSON Web Token (JWT) は標準規格(RFC 7519)で定義されています。この仕様は認証、認可の情報をJSON形式でシステム間で安全にやりとりする際に使用できる、コンパクトで自己完結型のフォーマットを定義しています。デジタル署名を付与することもできるため、検証可能で信頼がおけます。JWTへの署名には共通鍵を使用するHMACアルゴリズムあるいは、公開鍵/秘密鍵ペアを使用するRSAまたはECDSAアルゴリズムを使用できます。
JWTは機密性を保つために暗号化することもできますが、ここでは 署名されたJWT に焦点を当てます。署名されたJWTは完全性を検証でき、暗号化されたトークンは内容を他者から隠すことができます。JWTが秘密鍵を用いて署名された場合、その署名は公開鍵を使うことで、秘密鍵を持つ当事者だけがそのJWTに署名した者であることが確認できます。
JSON Web Token (JWT)をいつ使用すべきか
JSON Web Token (JWT) が役立つ用途には次のようなものがあります。
-
認証および認可:JWTを使用する最も一般的な利用用途です。ユーザーがログインすると、その後の各リクエストにはJWTが含まれ、ユーザーはそのJWTで許可されたエンドポイント、リソースにアクセスできます。オーバーヘッドが小さく、異なるドメインのシステム間で簡単に使用できるため、シングルサインオンやSPAやモバイルアプリケーション、APIの呼び出しなどでJWTが使用されています。
-
情報交換:JSON Web Tokenは、システム間で認証や認可の情報を安全にやりとりする際に優れた形式です。JWTには署名できるため、例えば公開鍵/秘密鍵のペアを使用して、送信者が本人であることを確認できます。さらに、署名はヘッダーとペイロードを使って計算して作成されるため、コンテンツが改ざんされていないことも確認できます。
JSON Web Token (JWT)を使用する理由とは
JSON Web Token (JWT) の利点について、Simple Web Token (SWT) および XML を使用する Security Assertion Markup Language Token (SAML) と比較してみましょう。
JSONはXMLよりも冗長でないため、エンコード後のサイズも小さくなり、JWTはSAMLよりもコンパクトになります。このため、JWTはHTMLやHTTPの環境に適した選択肢となります。
セキュリティ上の観点でいうと、SWTは共通鍵を使用するHMACアルゴリズムによってのみ署名できます。SAMLでは公開鍵/秘密鍵のペアを使用する複数のアルゴリズムから選択して署名できます。しかし、セキュリティホールを発生させずに、XMLデジタル署名を使用してXMLに署名することは、JWTへの署名のシンプルさに比べて非常に複雑です。
JSONパーサーは、オブジェクトに直接マッピングできるため、ほとんどのプログラミング言語において一般的になっています。しかし、SAMLで使用されるXMLにはドキュメントからオブジェクトへの自然なマッピングはできません。そのため、JWTを使用する方が、SAMLよりも簡単です。
利用実績についていうと、JWTはインターネット規模で使われています。これは複数のプラットフォーム、特にモバイルでのJWTのクライアント側処理の容易さを表しています。
エンコードされたJWTとエンコードされたSAMLの長さの比較
JSON Web Token (JWT)の構造
署名が付与されたJSON Web Tokenは、ドット(.)で区切られた次の3つの部分で構成されるコンパクトな形式です。
- ヘッダー
- ペイロード
- 署名
したがって、JWTは通常、次のようになります。
xxxxx.yyyyy.zzzzz
詳しくみてみましょう。
ヘッダー
ヘッダーは通常、JWTであるタイプと、HMACとSHA256の組み合わせ(HS256)やRSAとSHA256の組み合わせ(RS256)などのハッシュアルゴリズムの2つの項目で構成されています。
例 :
{"alg": "HS256","typ": "JWT"}
このJSONは__Base64 URL__でエンコードされ、JWTの最初の部分になります。
改行とスペースを削除してからエンコードすると、出力は次のようになります。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
ペイロード
2番目の部分は、クレームを含むペイロードです。クレームは、エンティティ (通常はユーザー) と追加のメタデータに関する情報です。クレームには予約済み、パブリック、プライベートの3種類があります。
- 予約済みのクレーム:標準規格で定義されているクレームです。必須ではありませんが、相互運用可能なクレームを使用でき、利便性も高いため使用が推奨されています。その中には、iss(発行者)、exp(有効期限)、sub(サブジェクト)、aud(オーディエンス)、その他のクレームなどがあります。
-
パブリッククレーム:JWTを使用するユーザーは、これを自由に定義できます。しかし、衝突を回避するにはIANA JSON Web Token (JWT) レジストリで登録するか、衝突に強い名前空間を含むURIとして定義する必要があります。
-
プライベートクレイム:これらのカスタムクレームは、その使用に同意したパーティ間での情報共有を可能にするものであり、予約済みのクレーム、あるいはパブリックのクレームではありません。
ペイロードの例は次のとおりです。
{"sub": "1234567890","name": "John Doe","admin": true}
ペイロードは__Base64 URL__ エンコードされ、JWTの2番目の部分を形成します。
ヘッダーで行ったようにペイロードをエンコードすると、出力は以下のようになります。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
署名
署名部分を作成するには、エンコードされたヘッダー、エンコードされたペイロード、シークレットをヘッダーで指定した署名アルゴリズムを使わなければなりません。
たとえば、HMAC SHA256アルゴリズムを使用する場合、署名は次のように作成されます。
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
署名はメッセージが途中で変更されていないことを検証するために使用されます。秘密鍵で署名されたトークンは、JWTの送信元が正しくそのシステムであることを検証できます。以下は、これまで例にしてきたヘッダーとペイロードがエンコードされ、シークレットで署名されたJWTです。
出力はドットで区切られた3つのBase64 URL エンコードした文字列であり、HTMLおよびHTTP環境で簡単に渡すことができ、SAMLなどのXML標準に基づくものに比べてコンパクトです。
次のセクションでは、JWTデバッガーを使って、JWTのデコード、検証、作成を行ってみましょう。
デコード
JSONWeb Token(JWT)がどのような構造で、どのように機能するかという基本を学びましたので、その知識を次のパズルで実践してみましょう。
- JWTは複数のピースに分割されています。
- 目標は、以下の文字列のピースを正しく並べ直して、有効な署名を持つJWTを作成することです。
- これは、有効なJWTヘッダーとペイロードを形成する方法の練習です。
- また、JWT署名を検証するために、シークレットをデコードする練習もします。
cCI6IkpXVCJ9eyJhbGciOiJIvbd6js8G66H3Me69RqSE-0w3eyJuYW1lIjoiLCJpYXQiOjE2PtEH73_fTnj4bevBfNEUzI1NiIsInR5RGV2RGF5MjMiODQyNDU2MDB9
まず、最初のピース「cCI6IkpXVCJ9」を選び、macOSとLinuxで「base64」コマンドを使ってデコードしてみましょう。
echo 'cCI6IkpXVCJ9' | base64 --decode
Windowsでは、「certutil」コマンドを使ってファイルをデコードできます。
「sample-JWT.txt」というファイルを作り、その中にピースを入れてみましょう。
certutil -f -decode sample-JWT.txt output.txt
結果は次のようになります。
p":"JWT"}
さらに、残りのピースもデコードしていき、先に説明した構造に従って、ヘッダーとペイロードを正しい順番に並べて有効なトークンを作成してみましょう。
デコードを自動化するために、自身が使いやすいプログラミング言語で簡単なプログラムを作ることもできます。
次のセクションでは署名を検証する方法を学習します。
署名を検証
このセクションでは、jwt.ioデバッガーを使ってJWTの署名を検証する方法を取り上げます。
先ほどの手順に従いピースを組み立て、以下のJWTを得ることができたとしましょう。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiRGV2RGF5MjMiLCJpYXQiOjE2ODQyNDU2MDB9.PtEH73_fTnj4vbd6js8G66H3Me69RqSE-0w3bevBfNE
ブラウザでjwt.ioを開き、JWTを「Encoded」セクションに貼り付けると、Invalid Signature と表示されます。
これは、ページ上のデフォルトのシークレット(この場合、「your-256-bit-secret」)が実際のシークレットと一致しないためです。
今回のJWTで設定されているシークレットは、「useauth0byoktatobuildyourcustomidentitypipeline」(スペースなし、ピリオドなし、すべて小文字)です。
正しいシークレットを入力すると、ページ上に「Signature Verified(署名が検証されました)」という表示に変わります。
おめでとうございます。有効なシークレットを指定することで、このJWTの署名を正しく検証できました。
付録: JSON Web Token (JWT)の利用例
認証では、ユーザーが資格情報を使用して正常にログインすると、JSON Web Token (JWT)が返されます。トークンは資格情報であるため、セキュリティの問題が起きないように細心の注意を払う必要があります。一般的に、トークンは必要以上に長く保持するべきではありません。
保護されたエンドポイントにユーザーがアクセスしたい場合、ブラウザなどのクライアントはJWTを送信する必要があります。通常は、Bearerスキーマを使用してAuthorizationヘッダーで送信します。したがって、ヘッダーの内容は次のようになります。
Authentication :Bearer <token>
これはステートレスな認証メカニズムになります。サーバーの保護されたエンドポイントは、Authorizationヘッダーに有効なJWTがあるかどうかを確認し、存在した場合には、ユーザーは保護されたリソースへのアクセスを許可されます。JWTにリクエストされた操作に必要なユーザーについてのデータが含まれていれば、データベースへの問い合わせを減らせる可能性があります。ただし、必ずしもそうなるとは限りません。
JWTトークンをHTTPヘッダーで送信する場合は、トークンのサイズが大きくなりすぎないようにする必要があります。サーバーによっては、8KB以上のヘッダーを受け付けないものもあります。例えばユーザーの権限を管理する場合は、トークンに権限情報を集中させるのではなく、Fine Grained Authorizationのようなソリューションの利用も検討できます。
以下の図は、JWTがどのように取得され、APIやリソースへのアクセスに使用されるかを簡略化して示しています。
- アプリケーションまたはクライアントは、認可サーバに認可を要求します。これは、幾つかの認可フローのいずれかを通じて実行されます。例えば、典型的なOpenID Connect準拠のウェブアプリケーションは、認可コードフローを使って「/oauth/authorize」エンドポイントにアクセスします。
- 認可が行われると、認可コードがウェブアプリケーションに渡され、ウェブアプリケーションはこの認可コードを使用し「/oauth/token」エンドポイントに対してアクセストークンをリクエストします。認可サーバーはアクセストークンをアプリケーションに返します。
- アプリケーションはアクセストークンを使って(APIのような)保護されたリソースにアクセスします。
署名付きトークンの場合、トークンに含まれるすべての情報は、ユーザーやアクセスを許可をしているサードパーティーに公開されます。その情報が変更されることはありませんが、トークンの中にユーザやサードパーティに知られたくないような機密情報を入れないようにご注意して下さい。
まとめ
このラボでは、以下の内容について学びました。
- JSON Web Token(JWT)の概要
- JWTの構造
- Base64でエンコードされた文字列のデコード方法
- JWTにおける署名の検証方法
JWTについてさらに詳細を知りたい場合は、JWTハンドブックをダウンロードしてご覧ください。