JWT で学ぶトークン認証

こんにちは!
Web バックエンド担当の友廣です。

「Web エンジニアなら知っておきたい」ということで、今回は 「トークン認証と JWT」 についてです。

トークン認証とは ?

トークン認証とは、ユーザーの認証情報をトークンとして生成し、それを使って認証を行う仕組みです。

例えば、ユーザーが ID とパスワードを使ってログインすると、サーバーはトークンを発行し、クライアントに返します。 クライアントはトークンをリクエストに付与してサーバーに送信します。 サーバーはリクエストに含まれているトークンを検証し、リクエストの許可を判断します。

これにより、リクエストごとに検証を行いつつも、ユーザーが認証情報を都度入力する必要がなくなり、セキュリティと利便性の両方が向上します。

トークンの特徴

トークンの特徴として以下のようなものがあります。

  • 見た目上はランダムな文字列
  • サーバーはトークンを保持しない(ステートレス)
  • 有効期限がある
  • 通常、改ざんを検知できるように秘密鍵で署名されている
  • 通常、Authorization header に付与される
  • 大きく 2 タイプのトークンがある
    • 認証サーバーで有効かどうかを判断するためだけに使われるタイプ
    • トークン自体に意味を含んでいて、クライアント側でもその情報を見ることができるタイプ

今回は 2 タイプのうち、後者のタイプに含まれる JWT (JSON Web Token) を使った方法に焦点を絞って説明していきたいと思います。

JWT とは?

JWT は認証や情報交換の際に、エンティティ間(例えば、クライアントとサーバー)でセキュリティを確保しつつ、情報を軽量に伝達することを目的に RFC 7519 で標準化された規格です。 名前の通り JSON 形式のトークンを扱えるようにする仕組みになります。

JWT の構造

JWT にはトークン自体に様々な意味を持たせるためにクレームというものがあります。 クレームはエンティティ間で情報を送信するために使用されます。

クレームに含める内容はユースケースによって異なりますが、例えば、トークンを発行した人物、トークンの有効期間、クライアントに付与された権限などを含めます。

JWT は、 一般的に xxxx.yyyy.zzzz のような文字列でピリオド (.) で区切られた 3 つの部分で構成されます。 xxxx の部分をヘッダー、 yyyy の部分をペイロードと呼び、それぞれ JSON を BASE64URL エンコードした文字列になります。 zzzz の部分は署名になります。

ヘッダー

ヘッダーには、トークンのタイプと関与する署名アルゴリズムを定義します。 クレームとして algtyp の2つのクレームを記述することが多いです。

alg は署名アルゴリズムを指定するためのクレームです。

typ はトークンのタイプを指定するためのクレームです。

ペイロード

ペイロードにはクレームが含まれます。 JWT には必須のクレームはありませんが、使用頻度が高いものをパブリッククレームとして登録してあります。

以下はパブリッククレームの説明です。

省略名 項目名 説明
iss Issuer JWT 発行者の識別子
sub Subject JWT の用途を示す値。ユーザーの識別子などに使われる
aud Audience JWT の対象となる受信者の識別子
exp Expiration Time JWT の有効期限
nbf Not Before JWT が処理されるべきでない時間。JWT が有効になる日時を示す
iat Issued At JWT が発行された時間
jti JWT ID JWT の一意の識別子。異なる発行者の JWT の衝突防止

これら以外にもクレームを使いたい場合は、自由に定義しても問題ありません(プライベートクレーム名と呼ばれます)。

ただし、パブリッククレーム名と異なり、衝突の可能性があるため、通信するシステム間での同意が重要です。

署名

ヘッダーとペイロードをエンコードしてシリアル化した状態でもエンティティ間で情報の伝達は可能ですが、改ざん可能であるためセキュアではありません。

そこで、JWT は JSON Web Signature (JWS) を使って署名を加えることで改ざんを検知できるようになります(RFC7515)。

署名付きのJWT の作り方ですが、ピリオドで結合されたヘッダー.ペイロードの文字列を、認証サーバーだけが持つ秘密鍵を使ってHMACによる署名を生成し、生成した署名 BASE64URL エンコードします。 そして、ヘッダー.ペイロードの文字列の後ろにエンコードされた署名をピリオドで結合します。

リクエストを受け取る側は公開鍵で署名を復号化して、ヘッダー・ペイロードが同一かを検証することができるため、よりセキュアに情報伝達ができるようになります。

JWT のメリット

これまでの説明から JWT には以下のようなメリットがあると言えます。

  • 情報伝達できる
  • セキュアである

また、この 2 つのメリットからサーバーはセッション情報を持たないで認証ができるようになります(ステートレスな認証ができる)。

このため、JWT 認証はクロスオリジンの認証がセッションベース認証よりも容易であり、マイクロサービスアーキテクチャに適しています。

JWT のデメリット

JWT のデメリットとして無効化が難しいことがあります。 JWT はサーバーで管理せずに認証を行うため、JWT が悪意のある第三者に渡ると、その者がリソースにアクセスできるリスクがあります。

また、JWT を削除することが難しく、アクセスを停止するのも困難です。 一般的に、JWT が流出した際のリスクを緩和するために、有効期限を短くする対策が取られますが、期限を短くするとユーザーが再認証を求められる回数が増え、ユーザー体験が低下する可能性があります。

JWT 検証の流れ

JWT 認証の流れを見てみましょう。 ここでは、ユーザーがすでにアカウントを作成し、ログインしてからユーザー情報を要求するシナリオを想定しています。

  1. クライアントはユーザーの認証情報(例えば、ユーザー ID とパスワード)でログインを試みます。

  2. アカウント管理サーバーは認証情報が正しいか確認します。

  3. 認証情報が正しければ、アカウント管理サーバーはユーザーIDなどを使って JWT を作成します。この際、JWT の署名にはアカウント管理サーバーのみが持つ秘密鍵を使用します。

  4. アカウント管理サーバーは作成した JWT をクライアントに渡します。

  5. クライアントは渡された JWT を Cookie などに保存します。

  6. クライアントはユーザー情報をアプリケーションサーバーにリクエストします。この際、Authorization header に JWT を含めます。

  7. アプリケーションサーバーはリクエストに含まれた JWT を検証します。まず、ペイロードの確認を行います(exp が期限切れでないかなど)。次に、公開鍵で署名を複合化し、リクエストの JWT のヘッダーとペイロードと比較します。

  8. JWT が有効であれば、アプリケーションサーバーはクライアントに成功レスポンスを返します。

JWT に関連する技術

JWT は改ざん対策が可能ですが、盗聴防止はできません。 盗聴防止が必要な場合は、JWE(JSON Web Encryption)という技術があります。 JWE は JWT 全体を暗号化して、中身を見られないようにする技術です。

また、トークン認証には OAuth 2.0 や OpenID Connect といった方式もあります。

OAuth 2.0 は認可を委譲するための仕組みでアクセストークンを利用して、リソースへのアクセス許可を管理します。 これにより、リソースサーバーがサードパーティアプリに限定的なアクセスを許可できるようになります。

また、OAuth2.0 プロトコル上にアイデンティティレイヤーを加えたものが OpenID Connect になります。

これらの技術は JWT 認証を前提としているため、今回の記事が理解の助けになればと思っています。

まとめ

  • トークン認証はステートレスな認証である
  • JWT は JSON 形式のデータでそれ自体に意味を持たせることができる
  • JWT は JWS によって署名をつけることができるのでセキュアな認証ができる

次回の「Web エンジニアなら知っておきたい」シリーズもお楽しみに!

参考