Web認証におけるToken(JWT)とCookie(セッション)の違い

HTTP の世界はステートレスです.すなわち基礎的なプロトコル上では状態が管理されません.ユーザの認証状態などを管理するために,これまでは主に Cookie & セッション ID が使われてきました.一方で,最近ではトークンを使った認証状態の管理がよく使われるようになってきています.この記事では,この辺の参考文献を見ながらそれぞれの特徴(と主にトークンの利点)についてメモします.

それぞれの認証方式の概要を簡単に

Cookie & セッションID による認証

この方式では,ユーザが ID/PW をサーバに送り,認証結果としてセッション ID が Cookie として返されます.サーバ側では,払い出したセッション ID に紐づくセッション情報を裏で管理しておきます.ユーザはそれ以降の HTTP リクエストに Cookie(セッションID) を含めることで,サーバに自分が誰であるのかを示します.サーバは,送られてきたセッション ID と,裏で管理しているセッション情報を照合して,ユーザの認証状態を判断します.この方式では状態の管理を実現しており,ステートフルな環境と言えます.

トークンによる認証

トークンでの認証は,ユーザは認証結果として Cookie の代わりにトークンを受け取ります.サーバはトークンを払い出しますが,ユーザの状態に関する情報は何も管理しません.ユーザは HTTP リスエストにトークンを含めることで,サーバに自分が誰であるのかを示します.サーバは送られてきたトークンを検証し,そのユーザを認証します.この仕組みではサーバは状態管理から開放されており,ステートレスと言えます(クライアントに状態を保存しているとの見方もできなくはないですが).ちなみに,ここでいうトークンは事実上のスタンダードとも言える JWT(JSON Web Token) を想定しています.

トークン(JWT)の特徴

ステートレス

Cookie & セッションを使った認証管理では,サーバ側でユーザの状態情報を管理する必要があります.で,ロードバランサを使うような分散環境の場合には,複数のサーバ間でセッション情報を同期したりとか,ユーザと接続先サーバの組み合わせを固定するようにリクエストを振り分けるとかしなければならないわけです.この辺の話は過去に以下の記事を書いたのでそちらを参照.

norikone.hatenablog.com

で,トークンを使った認証では基本的にサーバ側で状態を管理しません.サーバ側でやることは,送られてきたトークンの検証だけです.なので,上記のような,サーバ側で認証状態をどうやって扱うかという話を考えなくていいわけです.状態情報とサーバが密に結合することがあるセッション方式と違って並列分散が容易で,スケーラブルであると言えます.まあ最近では,KVS やフレームワークを使えば水平分散環境でも割と簡単に状態管理は実現できるようになってきてはいると思います.ただ,そういった状態管理用の機能がそもそも不要という点で,コストの低減はできるかもしれません.

クロスドメイン

Cookie は有効範囲がドメイン単位で設定されます.あるドメイン(サイト)でもらった Cookie の情報は,他のドメインでは使えないということです.滅多矢鱈に Cookie の情報を渡してしまうのもよろしくなさそうなので,こうなっているのは妥当と考えられます.

しかし,Web API による第三者への機能提供が盛んになっている現在,このような制約は円滑なサービス提供の足枷となり得ます.1つの認証結果を複数の第三者サービスで使い回せれば効率的です.

CORS を有効にしたトークン方式では,Cookie 方式のような有効範囲の制限を解除できます.トークン認証を許可しているサービスであればどこでもトークン1つで利用できる世界です.SSO 的な発想ですね.アプリケーション側で状態を持たなくて済むということも,このようなサービス間連携における利点となります.

データストア

Cookie & セッションによる方式では,Cookie にセッション ID という単一の文字列を管理するだけに留まります.トークン(JWT)は,それ自体に様々なメタデータJSON として格納することが可能であり,表現力に優れています.

例えば,ユーザの権限情報なんかをトークン内に含めたりできるみたいです.使い方はそれぞれのアプリケーション次第といったところでしょうか.

パフォーマンス

リクエストのたびにユーザから送られてきたセッション ID で認証するというのは,毎回それをキーとしてデータベースを検索する処理が走ることを意味します(リクエストと管理情報のマッピングをとらなければいけないので).

トークン方式でのサーバの負担はトークンの検証だけで,データベースを検索する処理は不要です.このため,トークン方式のほうがパフォーマンスが高いみたいです.実際に評価したわけではないので何とも言えませんが,確かに検証動作の方がすぐに終わりそうな印象はあります.

また,先にトークンには任意のデータを格納できると書きましたが,例えばユーザのロール情報(管理者 or 一般)などを入れておけば,サーバ側でデータベースを検索してユーザのロールを判定するような処理をカットできたりします.このような使い方もできるという点で,トークンがよりパフォーマンスで優位であると言えるかもしれません.

ただ,トークンに格納するデータが過剰に大きくなると,情報の転送がボトルネックになって逆にパフォーマンスが落ちるということも考えられなくはなさそうです.

モバイル対応

Web API を使うのはブラウザだけではありません.ネイティブアプリや IoT デバイスなんかもガンガン API を使います.で,こういったネイティブ環境で Cookie を扱うのは割と面倒臭く,考慮事項が多いみたいです.IoT デバイスなんかは Cookie を管理する機能が備わってなかったりするかもしれません.

そこでトークンを使えば,このような Cookie を使うのが難しいような場合でも,比較的容易に実装できます.このような理由から,ブラウザ以外のクライアントでは多くの場合でトークンを使うのが簡単かもしれません.

ビッグサイズ

JWT はサイズが大きいという欠点があるみたいです(そこまで気にすることでもなさそうという印象ですが).トークンを管理する場所は LocalStorage か Cookie を選択できますが,Cookie では 4kb がデータサイズの上限なので,トークンに沢山の情報を埋め込もうとすると問題になるかもしれません.

XSRFXSS 耐性

XSRFXSS の説明は割愛しますが,XSRF が発生する原因に,Web アクセス時に自動的にアクセス先サイトに紐付いた Cookie を送ってしまうというものがあります.攻撃者はこれを利用し,間接的に Web アクセスを発生させてアクセス先で認証を得るわけです.

トークンは基本的に LocalStorage か Cookie で保管されますが,LocalStorage で保存することで勝手にアクセス先に認証情報を送ることはなくなるので(Cookie ではないので),XSRF を受けなくなります.トークンを Cookie に保存した場合には,同様の問題が発生しますが.

一方,LocalStorage にトークンを保存すると,今度は XSS を受けやすくなります.LocalStorage は JavaScript から容易にアクセスできるためです.逆に,トークンを Cookie で扱えば httpOnly 属性により XSS 対策ができます. つまり,トークンを管理する方法に応じて意識すべきセキュリティ対策が変わってくるということですね.