クッキー(Cookie)
クッキーとは何で、なぜ使用するのか?
ブラウザとサーバー間でデータを送信する際に使用されるHTTPは、前回のリクエストに関する情報を保存できない特徴があるため、各クライアントを識別しません。しかし、現代のウェブアプリケーションでは、複数の複雑な機能を実装したり利便性を高めたりするために、サーバーがウェブブラウザを個別に識別できるようにする必要があります(そうしないと、サイト内でページを移動するたびに毎回再ログインしなければならなくなるため、本当に困ります)。
そこで、サーバーはウェブブラウザにクッキーというものを送信し、各リクエストごとにクッキーを通じてブラウザに関する情報を提供することで、この問題を解決します。つまり、クッキーとはサーバーがクライアントを識別するために使用する情報をキーとバリューの形式でブラウザに保存したものと言えます。
種類
- セッションクッキー(session cookie): ウェブサイトを訪れた際に一時的に生成されるクッキーです。ブラウザを閉じると削除され、ログインユーザーの認証やショッピングカートの保存に使用されます。
- 永続クッキー(persistent cookie): ユーザーがウェブサイトに初めてアクセスしたときに生成され、特定の有効期限までブラウザに保存されるクッキーです。一般的にユーザーの言語設定やウェブサイトでの活動を追跡し、カスタマイズされた広告を提供するために使用されます。
セッション(Session)
クッキーを通じたユーザー認証時の問題点
ウェブサイトはクッキーを利用してどのようにログイン済みのユーザーを識別できるでしょうか。最も簡単に考えられるのは、クッキー自体にIDやパスワードを保存することです。しかし、クッキーはウェブブラウザに保存されているため、ユーザーが簡単にその情報を変更でき、XSSやCSRF攻撃などによってハッカーに容易に盗まれる可能性があります。これらのクッキーのセキュリティ問題のため、サーバー側でユーザーに関する認証情報を保存する必要があります。これがセッションIDであり、この認証方式をセッションベースの認証方式と言います。
セッションの概念的定義
ウェブ開発の文脈において、セッションとはユーザーのウェブサイトやウェブアプリケーションとの相互作用に関する状態情報を維持する方法を指します。ユーザーがウェブサイトを訪れると、サーバーはそのユーザーのためにセッションを作成できます。また、セッションを通じてサーバーはユーザーのログイン状態、設定、フォームに入力されたデータなどの情報を追跡できます。
ウェブセッションとは
ウェブセッションとは、ユーザーとウェブサイト間の相互作用の期間です。さらに、ウェブサイトはセッション中にユーザーの行動や設定に関する状態情報を維持します。サーバーはユーザーがウェブサイトを閲覧する際に、そのユーザーのセッションを開始できます。セッションはユーザーがログアウトするまでアクティブな状態を維持します。
セッションの多義性について
複数の情報を調べていると、「セッション」という用語がさまざまな意味で使用されているように感じます。クッキーとセッションを一緒に使用する場合、セッションはサーバー側に保存されたユーザー情報を指すようであり、またはユーザーとウェブサイト間の相互作用が行われる期間を指すように思えます。
動作過程
-
認証リクエスト: ウェブブラウザがIDとパスワードを送信してサーバーから認証を受けます(HTTPSを使用している場合、情報が盗まれる心配は減少します)。
-
セッションIDの生成と送信: サーバーは認証されたユーザーであることを確認し、固有でランダムなセッションIDを生成します。その後、ユーザーに関する情報とともにセッションストレージ(DB、ファイルシステムなど)に保存し、レスポンスメッセージの
Set-Cookie
ヘッダーにセッションIDを含めてブラウザに送信します。 -
セッションIDの送信と認証: ブラウザは各リクエストごとにセッションIDが保存されたクッキーをヘッダーに含めて送信し、サーバー側はセッションストレージを確認して該当するセッションIDが存在すれば、リソースへのアクセス権を付与します。
-
セッションの終了: セッションクッキーにセッションIDが保存されている場合、ブラウザが閉じられるとクッキーは破棄されます。永続クッキーに保存されている場合は、有効期限までのみ保存されます。サーバー側のセッションIDは有効期限まで再利用され、ユーザーが再度ログインする場合は再生成されます。
利点
上記の動作過程を見ていると、ブラウザのクッキーにもセッションIDが保存されることがわかります。これは、認証に必要な情報がハッカーに盗まれる可能性が高いことを意味します。HttpOnly
やSecure
などの設定を通じてこれを防ぐことは可能ですが、セッションハイジャックなどの盗難の可能性は依然として存在します。では、セッションベースの認証方式を使用する利点は何でしょうか?
セッションIDはランダムに生成できるため、たとえハッカーに盗まれたとしても、廃棄して再生成することが可能です。ただし、ハッカー側がセッションストレージなどを攻撃して情報を盗んだ場合は別ですが、クライアント側でセッションIDが盗まれたかをサーバーで検出するのは容易ではありません。したがって、これを防ぐための手段を講じ、セッションIDの有効期限を適切に設定することが重要です。
問題点
セッションベースの認証の問題点は、ユーザーのセッションIDをサーバー側で保存しなければならない点です。ログインユーザーの数が増えると、セッションIDを各リクエストごとに確認するため、I/Oリクエストが頻繁に発生し、サーバー側のデータリソース使用量が増加する問題があります。また、トラフィックの増加に対応してサーバーインスタンスを増やす場合、各インスタンス間でセッションIDを同期させるのにコストがかかります(詳細についてはこの文章を参照)。このような問題を解決するために登場したのがトークンベースの認証方式であり、現在のウェブ開発で頻繁に使用されているトークンがJWTです。
JWT(JSON Web Token)
トークンベースの認証方式はセッション認証方式に比べてどのような利点があるのか?
トークン認証方式はセッション認証方式とは異なり、ユーザーの認証情報をサーバーが管理せず、ブラウザが管理します。したがって、ユーザーが増えてもセッションを管理する必要がないため、サービスの負担が軽減され、セッションの同期に頭を悩ませる必要がありません。
しかし、クッキーの例を考えると、認証情報を完全にクライアント側が管理するのは危険ではないかという疑問が生じます。確かにセッションを通じて管理するよりは若干脆弱ですが、JWTの特徴を見てみるとその利点がわかります。
JWTの発行過程
JWTの発行過程では、サーバーはユーザーがログインを通じて認証を完了すると、JWTヘッダー、ペイロード、シグネチャの3部分に分かれたJWTトークンを発行します。シグネチャがJWTセキュリティの核心です。シグネチャは非対称鍵方式を使用しており、サーバーは自身が持つ秘密鍵を使用してヘッダーとペイロードを結合して作成します。後に、サーバーはユーザーから送信されたトークンのヘッダーとペイロードを秘密鍵で暗号化し、シグネチャを再作成します。そして、トークンのシグネチャと比較して改ざんの有無を判別し、正しければユーザーにデータへのアクセス権を許可します。
セキュリティの疑問とその解決
ここで疑問に思うのは、シグネチャを自由に復号可能にしてもセキュリティのある認証が可能なのかという点です。しかし、このシグネチャを暗号化できる鍵はサーバーが持つ秘密鍵のみであるためです。もし他人が管理者権限にアクセスするためにペイロードを修正しようとしても、再暗号化するにはサーバーの秘密鍵が必要なので、秘密鍵を知らなければJWTトークンを改ざんすることは不可能です。
拡張性の観点からの利点
さらに考えられるもう一つの利点は、秘密鍵を使用して有効性を判別する必要がなく、公開鍵だけでトークンの有効性を確認できることです。非対称鍵の特性により、シグネチャを公開鍵で復号し、ヘッダーとペイロードと比較して有効性を検証できます。したがって、他のサービスでもサーバーが発行した公開鍵を使用して有効性を検証できるため、アプリケーションの拡張性を高めることができます。
具体例:ソーシャルログインサービス
例えば、ソーシャルログインサービスAが認証主体であり、他のサービスBがユーザーの認証をAを通じて行うと仮定します。このとき、セッション方式を使用すると、ユーザーの有効性を検証するためにBは毎回サービスAに認証を要求する必要があり、これはB側でもA側でも好ましくありません。
まず、Aは自分のサービスを運用する際にもセッションを使用するため、他のサービスの認証のためにセッションDBにアクセスすると非常に大きな負荷がかかります。これはソーシャルログインサービスを利用するサービスが増えるほど、さらに深刻になります。
一方、B側ではAに認証を依存しているため、Aに問題が発生するとBも正常なサービスを提供できなくなります。
しかし、トークン方式を使用すれば話は異なります。Aはユーザーにトークンを発行するだけで、ユーザーはBにアクセスする際にAから発行されたトークンを使用すればよいのです。BはAにリクエストする必要がなく、公開鍵を使用してトークンを検証するだけで済みます。
トークン認証方式の問題点
JWTトークンはセッションIDに比べて多くの情報を含んでいるため、転送におけるオーバーヘッドが大きいです。また、セッションIDと同様に、ハッカーがユーザーのトークンを盗むと、そのユーザーになりすますことができるという問題点も同様に存在します。
したがって、サーバー側ではアクセス用のトークン(アクセストークン)の発行に加えて、リフレッシュトークンをクライアントに発行します。トークンの有効期限が切れると、クライアント側が送信したリフレッシュトークンの有効性を検証した上で新しいアクセストークンを発行します。問題は、リフレッシュトークンまで盗まれてしまうと対処が難しいという点です。通常、アクセストークンが頻繁に盗まれるため、有効期限を短く設定し、リフレッシュトークンは長く設定しますが、この場合サーバー側では非常に長期間ハッカーが自由にログインできないようにすることが困難です。
したがって、リフレッシュトークンをDBに保存し、ユーザーがログインた際にアクセストークンを再発行するなどの方法でこれを防ぐ必要があります。しかし、これによりサーバーはユーザーがどのアクセストークンを保持しているかを識別する必要があり、結果としてセッション認証方式と似たようなステートフルな方式になってしまいます。
しかし、それにもかかわらず、前述のようにJWTのみでも認証が可能な特性のおかげで、拡張性が高いため、複数のサービスが分散されたマイクロサービスアーキテクチャでは有用であると言われています。