セッション管理にJWTを使うと何が変わるの?
はじめに
Webページとサーバ間のセッションを管理する方法として「Session」と「JWT」の二種類の方法が一般的だと言われています[1]。それらのワードで検索すると、たまに「Session vs JWT」という記事がヒットします。
個人的にこれは誤解を生みそうな表現に思います。私だけかもしれませんが「JWTという今までのセッション管理とは全く異なる認証状態の管理方法があるのか?」と誤解してしまいました。
しかしJWTを理解するための正しいイメージは「Session vs JWT」ではなく、上の図のように「セッション管理のやりとりに使うデータとしてSession IDを使うかJWTを使うか」だと思います。
この記事ではまずSession IDを用いたセッション管理を説明し、次にJWTを使ったセッション管理について述べ最後にJWTのメリットを紹介します。
Session IDを使ったセッション管理
Webページからユーザ名とパスワードを用いてユーザ認証を行い、認証状態の管理を行う流れは下の図のようになると思います。
まずログインが成功するとSession IDという「Webページ(ユーザ)とサーバしか知らないランダムな文字」が作成されます。そして以降のリクエストに 有効なSession ID が設定されていれば「ログインしたユーザからのリクエストである」と判断できます。
このとき有効なSession IDかどうかは、有効期限が切れていないSession IDがDBに存在するかどうかでチェックします。
悪意を持った攻撃者が、適当なSession IDを総当たり的に試すような攻撃を仕掛けてくる可能性があるためSession IDは十分な長さであること/推測されにくいこと/頻繁に更新することが求められます。
JWTを使ったセッション管理
JWTを用いた場合は以下のような流れになります。ログインや、JWTをWebページに返すところなどはSession IDを用いた場合と比べて変わりません。
一つ目の違いは、DBにアクセスせずともJWTの有効性を確認できている点です。これはJWTの仕様[2]として、JWTの中に「サーバの秘密鍵で署名されたデータ」を入れているからです。
署名付きJWTは「JWT自身とサーバの公開鍵を用いて」その有効性(JWTが改竄されていないこと)を検証できます。
(公開鍵暗号や署名のお話は、長くなるので別の記事で書きたいと思います。。。)
二つ目の違いは、Session IDにはランダムな文字列を使用していましたが、JWTには任意のデータをJSON形式にしてBASE64URLエンコードした文字列を使用します。そのため、セッション管理に使う文字列の中にユーザの情報やJWTの有効期限などの情報を保存しておくことができます。結果として「DBにアクセスせずに」リクエストしてきたユーザの情報をJWTから取得できます。
「DBにアクセスせずに・・・」というメリット
Session IDとJWTの根本的な違いは「DBにアクセスせずに検証・情報を取得できる」ということでしたが、その一般的なメリットはスケーラビリティを向上させることができるという点があります。
以下の図のように、Session IDやJWT用いたそれぞれのケースでユーザに関する情報をどのタイミングで得られるかを考えるとイメージしやすいと思います。
JWTはサーバの公開鍵があれば検証可能なので、ロードバランサでリクエストを受け取ったタイミングでリクエストの検証が可能です。またリージョン情報などをJWTに保存しておけば、どのサーバにルーティングするべきかもJWTだけで判断することができます。
JWTのデメリット
JWTのデメリットとしては、JWTのサイズがSession IDに比べて大きくなることや、JWTの中身を更新する仕組みが複雑になることが挙げられます。後者の弱点をカバーするために、なるべくイミュータブルな情報(ユーザIDなどの変更がない情報)のみをJWTで扱う方がいいと私は考えています。
またJWTにはユーザ情報を保存しているにもかかわらず暗号化はされていないので、JWTの中にどのようなデータが保持されているか閲覧することは可能です。しかし私自身は、ことセッション管理においては下記の理由から、それほど問題と捉えていません。
- ユーザ自身にJWTの中身を見られるケース => サービス提供者がユーザに対してどのようなメタデータを付与しているか分析されるだけ。
- 第三者にJWTの中身を見られるケース => JWTに限らずそもそもあってはいけないこと。セッションハイジャックされている。
個人的によくやるJWTの利用法
私がサービスを作るときはインフラをAWSで構築しています。AWSにはCognitoという認証サービスがあり、メールアドレスとパスワードを渡して認証が成功するとJWTを返してくれます。このJWTの中に以下のようなデータを保存するよう設定しています。
- テナントID(組織ID)
- ユーザID
- ユーザ権限
- メールアドレス
特にマルチテナント型サービスを作る際、PostgreSQLのRLS(Row level security)を使うのですが、JWTからテナントIDを取り出してRLSで保護されたデータのみにアクセスする仕組みを簡単に実装することができます。
JWTを使わない場合は、リクエストのたびにRLSで保護されていないセッションテーブルに問い合わせを行い、テナントIDを引っ張り出してこないといけません。
Discussion