2017-5-25

クッキーに関して

雑な技術メモ

バックエンドを Node.js に切り替えようとして Express 使って express-session を使ってセッションを扱おうとしたら Cookie に関するオプションがよくわからなかったのでまとめておく。

サーバー側からの送信方法

HTTP ヘッダーにSet-Cookieを付与することによって送信出来る。

例えば、以下のような形。

Set-Cookie: hoge_cookie=var; path=/; httponly

パラメータに関しての説明は後述。

クライアント側での取得方法

サーバーから送られた Cookie のデータは、document.cookieに保存されるので、単純に以下のコードで取得することが可能。

var cookie = document.cookie;

ただ、これで取得できるのはオブジェクトではなく、fuga_cookie=hogeのような単純な文字列なので扱いづらい。cookie.js等のライブラリを使用したほうが良い。

Set-Cookie の各種パラメータ

NAME=VALUE

Cookie の本体、キーバリュー形式で値をクライアントに保管する。終わり。

expires

Set-Cookie: fuga=hoge; expires=Tue, 19 Jan 2038 03:14:07 GMT

みたいな感じで使用する。expires に指定された日付まで Cookie が有効、超えると無効になる。

cache-control でも見たなこれ。

max-age

Set-Cookie: fuga=hoge; max-age=3600

みたいな感じで使用する。max-age に指定された秒数間 Cookie が有効。

expires と同時に指定された場合は max-age が有効になる。

cache-control でも(ry

domain

Set-Cookie: name=value; domain=example.com

Cookie が有効なドメインを指定する。現在のホストと domain で指定された物が後方一致すればその Cookie は有効になる。

なお、domain には現在のホストと全く関係の無いものを設定しても無効になる。

例えば、http://www.sd-milieu.netにアクセスがあってdomain=www.sd-milieu.netなら有効だが、domain=google.comとかは無効。そりゃ他人のドメインの Cookie を操作できちゃうので当然っちゃ当然。

じゃあどんな時に使うのかって言うとhttp://www.sd-milieu.netにアクセスがあった際にdomain=sd-milieu.netとして(これは有効)、http://blog.sd-milieu.netでも Cookie を有効にしたりとかしたい場合に使う。同一ホスト間で Cookie をやり取りしたい場合に使う。多分。(試してない)

path

Set-Cookie: name=value; path=/

Cookie が有効なパスを指定。現在のパスと path で指定された物が前方一致すれば有効になる。

例えばpath=/blogの場合、アクセスがあった URL がhttp://www.sd-milieu.net/blog/なら Cookie が有効だが、http://www.sd-milieu.net/about/なら無効になる。パスが違うから。

secure

このオプションが設定されている Cookie は、https 通信の時のみ送信される。

セッションを利用する場合は、セッションを利用するページは全て https にしておきこの secure オプションをセッション ID に付与しておかないと、http 通信をしているページでもセッション ID が通信に平文で乗ってしまって MITM 攻撃でセッションハイジャックされるよ、って感じなのかなと。

httponly

このオプションが付与された Cookie は、JavaScript から参照できなくなる。

XSS 脆弱性があったとしても、このオプションをセッション ID に付与しておけばセッションハイジャックを防ぐことが出来る。セッション ID には付けよう。

検証のために書いた Node.js のコード

var http = require("http");
var url = require("url");
var path = require("path");
var fs = require("fs");


var server = http.createServer();
server.on('request', function(req, res) {
  var uri = url.parse(req.url).pathname;
  var filename = path.join(process.cwd(), uri);

  fs.exists(filename, function(exists){
      if (!exists) {
        console.log('file or directory not found');
        return;
      }

      if (fs.statSync(filename).isDirectory()) {
        filename += '/index.html';
      }

      fs.readFile(filename, "binary", function(err, file) {
        if (err) {
          console.log('file can\'t read.');
          return;
        }

        var extname = path.extname(filename);
        var header = {
          "Set-Cookie": ["hoge_cookie=var; path=/; httponly", "fuga_cookie=test;"]
        };
        res.writeHead(200, header);
        res.write(file, "binary");
        res.end();
      });
  });

});
server.listen(8000);

参考

Cookie の仕様とセキュリティ