CBcloud Advent Calendar 2019の24日分の記事になります。
Cookieとは
簡単に言えば、Cookieとはウェブサイトの情報をブラウザで保存する仕組みです。
HTTPのヘッダーをインフラとして実装されていて、最新の仕様書はRFC6265です。
主な流れ
-
ブラウザ側
- ウェブサイトに向けてHTTPリクエストを送信する
-
サーバー側
- HTTPヘッダにSet-Cookieヘッダをセットしてレスポンスを返す
Set-Cookie: LAST_ACCESS_TIME=15:30
- HTTPヘッダにSet-Cookieヘッダをセットしてレスポンスを返す
-
ブラウザ側
- レスポンスの
Set-Cookie
ヘッダの値を保存する。 - 次回アクセス時には次のような形式でリクエストを送信する
Cookie: LAST_ACCESS_TIME=15:30
- レスポンスの
なにが嬉しいのか
以上のような非常に単純なやりとりを経ることによって、サーバー側はこのクライアントが最後にアクセスしてきた時間を知ることができます。
HTTPはステートレスを基本とした仕組みですが、Cookieをうまく使うことによってステートフルであるかのようなアプリを作ることができます。
もちろんブラウザ上でもJavaScriptを通して取得することが可能です。適当なウェブサービスで開発者ツールを開き、下記のコードを打てばString型でCookieが格納されていることが分かります。
console.log(document.cookie)
アンチパターン
便利な機能ですが、いくつかの制約からアンチパターンももちろん存在します。
まず永続性の問題です。ブラウザの設定によってはサーバーからのSet-Cookie
ヘッダを無視することもありますし、履歴消去などで一緒に削除されることもよくあります(実際消した事ある人も多いはずです)。
容量の問題もあります。最大容量は4kbです。通信のたびに毎回HTTPのヘッダに付与されるので単純に通信量が増大します。
セキュリティにも問題があります。HTTPは常に平文で送受信されるため、クリティカルな情報を与えてしまうと漏洩の可能性があります。
また、SSL/TLSを利用していたとしてもユーザーから自由に見たり、編集したりできることも問題で、ログインに必要なIDなど書き換えられると誤作動につながるような情報を入れるのにも適しません。
制約を与える
Cookie周辺で使えるHTTPヘッダーは、Set-Cookie
とCookie
以外にもRFCによって定められたいくつかのヘッダーが存在します。
-
Expires,Max-Age
クッキーの寿命を設定する -
Domain
ブラウザからCookieを送信する対象のサーバーを設定する -
Path
ブラウザからCookieを送信する対象のサーバーのパスを設定する -
Secure
https以外での通信のときはサーバーへCookieを送信しない -
HttpOnly
JavaScriptエンジンからCookieを隠すことができる(上記のようにdocument.cookie
で取れなくなる)。
Cookieでのセッション管理
Web関係の入門書などでよくサンプルとして取り上げられる認証の仕組みとして、Basic認証とDigest認証がありますが、今現在もっともよく使われている認証の仕組みはCookieを用いたセッション管理だと思います。以下にCookieを用いたセッション管理の流れを書きます。
セッション管理の流れ
- ブラウザ側からIDとパスワードを送信します。
- 注意点: 直接送信するため、SSL/TLSは必須です。
- サーバー側ではIDとパスワードで認証しセッショントークンを発行します。サーバー側はそのトークンをRDBなりKVSなりに保存します。そして、
Set-Cookie
にトークンを付与させてブラウザにレスポンスを返します。 - ブラウザは次回以降のアクセスでは
Cookie
ヘッダーにサーバーから発行されたトークンを付与してリクエストを送ることによって、サーバーからログイン済のユーザーであることが識別できるようにします。
以上が簡単なセッション管理の仕組みになります。時間経過による自動ログアウトなどのよく見る仕組みは、上記のExpires
やMax-Age
ヘッダを使うことによって簡単に実装できることが分かります。
例: Ruby on Railsでの署名付きCookieによるセッションデータの保存
上述のような、RDBなどにセッション管理用のテーブルを用意しユーザーの認証をする素朴な実装は悪くはないのですが、実はRailsなどのようなWebアプリケーションフレームワークは大抵の場合セッションストレージ機能を備えており、自前で実装する必要はないことがほとんどです。
Railsのセッションストレージ機能で用いられるActionDispatch::Sesstion::CookieStoreは、delete_session
とload_session
とnew
の3つしかメソッド持ちませんが、ブラウザに対して改ざんされないように電子署名付きのCookieを送信します。
ブラウザ側がそのCookieをRails側に再送すると、Railsは署名を確認します。ブラウザは何も特別なことはしておらず、署名も確認もRails側で行います。
この仕組みを用いることによるサーバー側のメリットは、データを保存する仕組みが必要ない点です。マイクロサービスにおいても、暗号化の仕組みだけ共通化しておけば、別にデータストアを立てる必要なくセッションデータを読み書きできるようになります。
おわりに
今回はサックリとまとめてしまいましたが、Cookieの歴史はWebセキュリティの歴史といってもいいほど試行錯誤がつまっており、Webアプリ作ってご飯を食べているWebエンジニア各位は一度しっかり時間を確保してCookieの仕組みなどを勉強してみるといいと思います。