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

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

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

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

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

トークン(JWT)の特徴

ステートレス

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

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 対策ができます.しかし,こうすると逆に Cookie の特性から,XSRF を受けやすくなります.

したがって,トークンを管理する方法に応じて意識すべきセキュリティ対策が変わってきそうです.