PyJWTを用いたアクセストークンの検証をやってみた

こんにちは織田です
今回はRAG-STDの開発中にPyJWTを用いてトークン検証を実施しましたので、そこで得た知見についてまとめようと思います。

JWTおよびPyJWTとは?

JWT (JSON Web Token) とは、インターネット上で情報を安全にやり取りするための規格の一つで、特に認証や認可の分野で利用されています。

ヘッダー、ペイロード、署名(シグネチャ)の3つの要素からなり、それぞれが”.”で区切られています。

そして、PyJWTとはJWT トークンをエンコード、デコード、検証してくれる Python ライブラリです。

JWTの各要素の詳細は以下の通りです。

ヘッダー (Header)

JWTのタイプ(通常は”JWT”)と、署名の生成に使用されるアルゴリズム(例: HMAC SHA256やRSA)がJSON形式で記述されます。これはBase64Urlエンコードされます。

例:

{

  "alg": "HS256",

  "typ": "JWT"

}

ペイロード (Payload)

ここに、JWTに含める情報(クレームと呼びます)が記述されます。クレームには以下の3種類があります。

  • 登録済みクレーム (Registered Claims): JWTの仕様で定義されているクレームで、iss(発行者)、exp(有効期限)、sub(主題)などがあります。これらは任意ですが、利用することで相互運用性が高まります。
  • 公開クレーム (Public Claims): 衝突を避けるためにIANAのJWTクレームレジストリに登録されているか、URIとして定義されているクレームです。
  • プライベートクレーム (Private Claims): 送信者と受信者の間で合意されたカスタムクレームです。

例:

{

  "sub": "1234567890",

  ""name": "John Doe",

  "admin": true

}

このペイロードもBase64Urlエンコードされます。

署名 (Signature)

エンコードされたヘッダー、エンコードされたペイロード、そして秘密鍵を使用して、ヘッダーで指定されたアルゴリズムによって生成されます。この署名があることで、トークンが改ざんされていないこと、および発行者が正しいことを検証できます。

これら3つの要素が「.」で連結されることで、最終的なJWTが構成されます。

例:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`

JWTを用いたトークン検証のプロセス

RAG-STD開発で実施したJWTを用いたトークン検証は、主に以下のステップで構成されます。

  1. クライアントからのJWT受け取り:
    フロントエンドから、ヘッダー情報を介してJWTを受け取ります。
  2. JWTの解析:
    受け取ったJWTをヘッダー、ペイロード、署名の3つの部分に分割します。
  3. 署名の検証:
    • 受け取ったJWTのヘッダーとペイロードを再度エンコードします。
    • サーバーが保持している秘密鍵(または公開鍵)と、エンコードされたヘッダー・ペイロードを用いて、新たな署名を生成します。
    • 生成された署名と、受け取ったJWTに付与されていた署名を比較します。両者が一致すれば、トークンは改ざんされておらず、有効であると判断できます。一致しない場合は、トークンが無効であると判断し、リクエストを拒否します。
  4. ペイロードの検証:
    署名の検証に成功したら、ペイロードに含まれるクレームを検証します。
    • `exp` (有効期限) クレームを確認し、トークンが期限切れでないかを確認します。期限切れであれば、トークンは無効です。
    • `iss` (発行者) クレームを確認し、信頼できる発行元からのトークンであるかを確認します。
    • 必要に応じて、`sub` (主題) やその他のカスタムクレーム(例: ユーザーID、ロールなど)を検証し、リクエスト元のユーザーが適切な権限を持っているかを確認します。

これらの検証ステップを全てクリアした場合、トークンは有効とみなされ、ユーザーは認証・認可された状態となります。

トークン検証時のポイント

実際のコードでは、フロントエンドから発行されたIDトークンの検証において、以下のような流れで処理を進めました。

  1. 公開鍵の一覧を取得: IDトークンを発行した認証プロバイダ(今回はMicrosoft)が公開している公開鍵の一覧(JWKS: JSON Web Key Set)を取得します。これは通常、特定のURLからJSON形式で提供されます。
  2. IDトークンの署名を検証する公開鍵を一覧の中から抽出: 取得した公開鍵の一覧から、検証対象のIDトークンのヘッダーにある`kid` (Key ID) に対応する公開鍵を特定し、抽出します。これにより、正しい公開鍵を使って署名を検証できます。
  3. 署名検証ありでデコード: 抽出した公開鍵とIDトークンを用いて、署名の検証を行いながらトークンをデコードします。このステップで、トークンが改ざんされていないか、正しい発行者によって署名されたものかを確認します。PyJWTのようなライブラリを使用すると、この署名検証はデコード処理の一部として自動的に行われます。
  4. ユーザー情報を抽出: 署名検証に成功したら、デコードされたペイロードからユーザ名やグループIDなどの必要なユーザ情報を抽出します。この情報をもとに、アプリケーション内でユーザーの識別や認可を行います。

その他のポイントとしては、以下のものがあげられます。

  • エラーハンドリング: 署名検証失敗、期限切れ、不正なペイロードなど、あらゆるケースで適切なエラーハンドリングを行うことが重要です。自分が実装しようとした当初はエラーハンドリングを適切に設定できておらず、エラーが発生しても原因が何か分からず悪戦苦闘しました…
  • ライブラリの活用: PyJWTを活用することで、ルーティングごとに検証処理を記述する手間を省き、コードの可読性と保守性を高めました。当初は「検証のコードを自前で用意しなければならないのか…」と不安でしたが、PyJWTを用いることで僅かな行数で実装できました。
  • `aud` (Audience) クレームの理解: トークンをデコードする際に、`aud` (Audience) クレームの検証に苦労しました。これは、トークンが「誰(どのサービス)のために発行されたものか」を示す、いわば手紙の「宛名」のような概念です。JWTを受け取る側(つまり、検証を行う私たちのアプリケーション)が、そのトークンの`aud`クレームに自身が含まれているかを確認することで、意図しない宛先に送られたトークンの利用を防ぐことができます。

まとめ

RAG-STDの開発を通して、JWTの仕組みと検証プロセス、そして実装上の注意点について深く理解することができました。検証用のコードを0から書くのは大変だろうと身構えていたのですが、PyJWTを使うことで、簡単に実装できました。また、コードを段階的に実装していくことで、検証に必要なプロセスについて詳しく理解することができました。この知見が、今後JWTを用いた認証・認可システムを構築される方々の一助となれば幸いです。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です