セッション管理に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を用いた場合と比べて変わりません。
一つ目の違いとしては、DBにアクセスせずともJWTの有効性を確認できている点です。これはJWTの仕様[2]として、JWTの中に「サーバの秘密鍵で署名されたデータ」を入れているからです。
署名付きJWTは「JWT自身とサーバの公開鍵を用いて」その有効性(JWTが改竄されていないこと)を検証できます。
(公開鍵暗号や署名のお話は、長くなるので別の記事で書きたいと思います。。。)
二つ目の違いは、Session IDはランダムな文字列を使用していましたが、JWTは任意のデータをJSON形式にしてBASE64URLエンコードした文字列を使用します。そのため、セッション管理に使う文字列の中にユーザの情報やJWTの有効期限などの情報を保存しておくことができます。結果として「DBにアクセスする前に」リクエストしてきたユーザ情報を利用できるようになります。
「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