読者です 読者をやめる 読者になる 読者になる

connectのセッションミドルウェアのcookieのsecure属性について

SSLCookie使う場合はsecure属性つけて非SSLの場合にはCookie送信しないようにするって仕組みがあるわけだけど、connectのsessionもその実装はされてる。

app.use(express.session({
  secret: yourSecret,
  cookie: { secure: true }
}));

こんな感じ(コードはexpressだけど)。で、こうするとSSLの場合だけCookieが有効になるはずなんだけど、herokuでやってたらSSLのときも有効にならなくて(Set-Cookieされない)なんぞと思って調べてみた。

現状(v1.8.5)のmiddleware/session.jsの該当のコードはこうなってる。

// only send secure session cookies when there is a secure connection.
// proxySecure is a custom attribute to allow for a reverse proxy
// to handle SSL connections and to communicate to connect over HTTP that
// the incoming connection is secure.
var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure);
if (secured || !cookie.secure) {
  res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
}

cookie.secureってのがmiddleware設定のオプションで渡した値なのでここではtrueになってる。ので

req.connection.encrypted || req.connection.proxySecure

このどっちかが真じゃないとSet-Cookieされないってことになる。んでこのreq.connection.encryptedってのはapp自体がhttps.Serverで起動してるときになんか値が入るらしい。でもherokuの場合はリバースプロキシがいてそいつからはhttpでリクエストがくるのでアプリ自体はhttp.Serverで立ち上がってる。だからreq.connection.encryptedはundefined。

んでreq.connection.proxySecureだけど、これはコメントに以下のように書いてある。

// proxySecure is a custom attribute to allow for a reverse proxy
// to handle SSL connections and to communicate to connect over HTTP that
// the incoming connection is secure.

つまりリバースプロキシを介したhttpsのアクセスはreq.connection.proxySecureを設定しとけってことかなと理解した。つまりこうすればいい。たぶん。

app.use(function(req, res, next) {
  if (req.header('x-forwarded-proto') === 'https') {
    req.connection.proxySecure = true;
  }
  next();
});
app.use(express.session({
  secret: yourSecret,
  cookie: { secure: true }
}));

x-forwarded-protoってのがリバースプロキシがアプリにアクセスするときにつけてくれるヘッダ。これで判定できるみたいなので、これを見てhttpsかどうかを判定すればOKっぽい。

ちなみにこれはconnectのv1.8.5の話で、次期バージョンのv2.xを見ると実装が変わっててこうなってる。

var cookie = req.session.cookie
  , proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
  , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
  , secured = cookie.secure && tls;

// browser-session cookies only set-cookie once
if (null == cookie.expires && !sessionIsNew) return;

// only send secure cookies via https
if (cookie.secure && !secured) return debug('not secured');

debug('set %s to %s', key, req.sessionID);
res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));

x-forwarded-protoが直接書いてあるのがわかる。ので2.xからはさっきみたいのは必要なくなるっぽい。んでtrustProxyってのがoptions.proxyになってるんで、proxyオプションを指定すればよさそう。

app.use(express.session({
  secret: yourSecret,
  proxy: true,
  cookie: { secure: true }
}));

まあこの辺はまだリリースされてないんで変わるかもしれんけど。

てかconnectの2.xとexpressの3.xはもう別のネームスペースで出したほうがいいんじゃないかと思い始めたよ。マル。