express-session 쿠키 설정이 안되는 이슈
최근 express와 함께 express-session을 사용하다가 생긴 쿠키가 발행되지 않는 이슈를 정리해보려고 합니다.
로컬 환경에선 express-session을 통해 발급된 쿠키를 Set-Cookie 헤더에 들어가며 잘 설정되고 있었지만, 개발환경에 배포하는 경우에 쿠키가 제대로 설정되지 않았습니다.
로컬과 개발 서버에서는 secure 설정과 도메인, same-site 정도의 차이가 있었습니다. 하지만 이런 경우, 대부분 쿠키가 헤더에 설정은 되지만 브라우저가 쿠키를 무시하거나 보내지 않는 문제였습니다.
하지만 이번 경우에는 Set-Cookie 헤더가 설정되지 않는 문제였습니다.
express-session 코드를 살펴보면 아래와 같은 부분을 확인할 수 있었습니다.
// express-session index.js
onHeaders(res, function(){
if (!req.session) {
debug('no session');
return;
}
if (!shouldSetCookie(req)) {
return;
}
// only send secure cookies via https
if (req.session.cookie.secure && !issecure(req, trustProxy)) {
debug('not secured');
return;
}
if (!touched) {
// touch session
req.session.touch()
touched = true
}
// set cookie
setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
});
Set-Cookie 헤더를 추가하기 전 secure 쿠키인 경우 요청이 secure 한 요청인지 체크하는 로직이 존재합니다. 실제로 이 부분에서 express-session이 secure 한 요청이라고 판단해 쿠키를 설정하지 않아 발생한 문제였습니다.
if (req.session.cookie.secure && !issecure(req, trustProxy)) {
debug('not secured');
return;
}
그럼 어떤 경우에 요청이 secure 하다고 판단할까요?
내부 구현은 아래와 같습니다. req는 express의 request, trustProxy는 express-session의 prxoy 옵션을 의미합니다.
function issecure(req, trustProxy) {
// socket is https server
if (req.connection && req.connection.encrypted) {
return true;
}
// do not trust proxy
if (trustProxy === false) {
return false;
}
// no explicit trust; try req.secure from express
if (trustProxy !== true) {
return req.secure === true
}
// read the proto from x-forwarded-proto header
var header = req.headers['x-forwarded-proto'] || '';
var index = header.indexOf(',');
var proto = index !== -1
? header.substr(0, index).toLowerCase().trim()
: header.toLowerCase().trim()
return proto === 'https';
}
1. 커넥션이 암호화 되어있는가?
express 서버가 https인지를 의미합니다.
2. express-session의 proxy 옵션이 false 인지
express 서버 자체가 https가 아니고 명시적으로 proxy가 없다고 지정해 주었기 때문에 더 이상 확인할 필요 없이 secure 하지 않다고 판단합니다.
3. express-session의 proxy 옵션이 설정되어있지 않다면 req.secure가 true인가
req.secure는 req.protocol === 'https'와 같다고 공식문서에서 이야기하고 있는데요. req.protocol은 express의 trust proxy 옵션에 따라 x-forwarded-proto 헤더를 기준으로 설정되기도 합니다.
4. express-session의 proxy 옵션이 true로 설정되어 있다면 x-forwarded-proto 헤더가 https 인가
명시적으로 express-sesison proxy 옵션이 true로 설정되어 있다면 x-forwarded-proto 헤더를 기준으로 secure 한 지 확인합니다.
정리
express-session의 proxy 옵션이
1. true 이면
x-forwarded-proto 헤더를 사용하겠다.
2. false 이면
실제 커넥션 자체가 TLS/SSL이 아니라면 secure 하다고 판단하지 않겠다.
3. undefined 이면
express의 설정에 의존하겠다. 따라서 proxy가 존재한다면 반드시 express의 trust proxy를 활성화해주어야 합니다.
X-Forwarded-*
그렇다면 X-Forwarded-Proto라는 값이 무엇이길래 express도 express-session도 해당 헤더를 사용하는 것일까요?
여러 proxy(ex. 로드밸런서)들을 거쳐 서버에 도달하게 되면 서버는 가장 마지막 proxy에 대한 정보만 받게 됩니다. 즉 마지막 proxy에서 http로 보냈는지 https로 보냈는지, proxy의 ip는 무엇인지 host는 무엇인지 와 같은 정보만 받게 됩니다. 하지만 서버 입장에서는 proxy의 정보뿐만 아니라 가장 처음 요청을 보낸 클라이언트의 정보들도 필요합니다. 이런 경우에, 사용하는 헤더가 X-Forwarded- 로 시작하는 헤더들입니다.
클라이언트와 proxy 간 통신에 사용한 프로토콜을 담고 있습니다.
클라이언트가 proxy에 요청한 원래 host 헤더를 의미합니다.
클라이언트와 클라이언트에서 서버에 도달할 때까지 거친 proxy들의 ip 주소를 담고 있습니다.