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

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

それぞれの認証方式の概要

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

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

トークンによる認証

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

トークン(JWT)の特徴

ステートレス

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

norikone.hatenablog.com

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

クロスドメイン

Cookie は有効範囲がドメイン(サイト)単位で設定されます.恐らく無関係のサイトに滅多矢鱈に Cookie の情報を渡してしまわないためでしょう.なので,あるドメインでもらった Cookie の情報は,他のドメインでは使えません.

しかし,第三者への Web API によるサービス提供が盛んになっている現在,このような制限は円滑なサービス提供の足枷になります.セキュリティが十分に確保されていれば,1つの認証結果を複数の第三者サービスで使い回すことが効率的ですね.

CORS を有効にしたトークン方式では,Cookie 方式のような有効範囲の制限がありません.トークン認証を許可しているサービスであればどこでもトークン1つで利用できる世界です(SSO 的).また,アプリケーション側で状態を持つ必要がなく,サービス提供のための状態情報をトークン内で管理できることも,そのようなサービス間連携における利点となります.

データストア

Cookie & セッション ID による方式では,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 対策ができます. つまり,トークンを管理する方法に応じて意識すべきセキュリティ対策が変わってきそうです.

おわり

ということで,主にトークンを使った認証方式についてざっくりとまとめてみました.トークンと言うと OAuth や OpenID Connect を連想しますが,それらについては以下の記事で概要をまとめています.おわり.

OAuth2やOpenID Connectとは何か、なんとなくわかった気になるための概要 - 備忘録の裏のチラシ