Node.js로 간단한 HTTP 서버를 만들면서 응답 헤더에 쿠키를 설정해 봤다.
const http = require("http");
const hostname = "127.0.0.1";
const port = 3000;
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Set-Cookie", [
"kundol=amumu; HttpOnly",
"loltier=master; Secure"
]);
res.end("응답!\n");
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
브라우저에 쿠키를 저장할 때는 Set-Cookie 응답 헤더를 사용한다. 뒤에 붙는 HttpOnly, Secure 같은 속성은 쿠키가 사용되는 범위를 제한한다.
HttpOnly가 설정된 쿠키는 브라우저의 document.cookie로 읽을 수 없다. XSS 공격으로 세션 쿠키가 탈취될 가능성을 줄이는 데 도움이 된다.
Set-Cookie: sessionId=abc123; HttpOnly
다만 JavaScript에서 값을 직접 읽을 수 없을 뿐, fetch()처럼 JavaScript가 실행한 HTTP 요청에는 조건이 맞으면 쿠키가 자동으로 포함된다.
Secure는 쿠키를 HTTPS 연결에서만 전송하도록 제한하는 속성이다.
Set-Cookie: sessionId=abc123; Secure
Secure만 붙였다고 JavaScript 접근까지 막아지는 것은 아니다. 세션 쿠키라면 일반적으로 두 속성을 함께 사용한다.
Set-Cookie: sessionId=abc123; HttpOnly; Secure
로컬 개발 환경에서는 브라우저가 localhost나 루프백 주소를 예외적으로 취급하기도 하지만 브라우저별 동작 차이가 있을 수 있다. 운영 환경에서는 HTTPS 사용을 전제로 하는 것이 안전하다.
세션 ID에는 이메일, 회원번호처럼 사용자를 유추할 수 있는 정보를 그대로 넣지 않는다. 충분히 길고 예측하기 어려운 임의의 값을 만들고, 실제 사용자 정보는 서버에서 관리하는 편이 안전하다.
또한 다음과 같은 속성을 함께 검토해야 한다.
Set-Cookie: sessionId=random-token; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800
HttpOnly: JavaScript에서 쿠키를 읽지 못하게 한다.Secure: HTTPS 요청에서만 쿠키를 전송한다.SameSite: 다른 사이트에서 시작된 요청에 쿠키가 전달되는 범위를 제한한다.Path: 쿠키가 전송될 URL 범위를 지정한다.Max-Age: 브라우저에 쿠키를 보관할 시간을 지정한다.쿠키의 만료 시간과 서버의 세션 만료 시간은 별개다. 브라우저에서 쿠키가 삭제되더라도 서버 세션이 계속 유효할 수 있으므로, 서버에서도 유휴 시간과 최대 유지 시간을 관리해야 한다.
세 가지 모두 브라우저에 상태를 저장할 수 있지만 사용 목적과 동작 방식이 다르다.
| 구분 | Cookie | LocalStorage | SessionStorage |
|---|---|---|---|
| 일반적인 용량 | 쿠키 하나당 약 4KB | 브라우저에 따라 약 5~10MB | 브라우저에 따라 약 5MB |
| 유지 기간 | Expires, Max-Age 또는 세션 종료 시점 | 직접 삭제하기 전까지 유지 | 해당 탭이나 창이 닫힐 때까지 |
| 접근 범위 | 도메인과 경로 설정에 따라 결정 | 같은 출처의 탭과 창에서 공유 | 같은 출처이면서 같은 탭에서 사용 |
| JavaScript 접근 | HttpOnly가 없으면 가능 | 가능 | 가능 |
| 서버 자동 전송 | 조건에 맞는 요청마다 전송 | 전송되지 않음 | 전송되지 않음 |
| 주요 용도 | 세션과 인증 상태 | 오래 유지할 사용자 설정 | 탭 단위의 임시 상태 |
쿠키와 Web Storage는 엄밀히 말하면 HTTP 캐시와는 다르다. 저장된 값을 애플리케이션에서 잘 활용하면 중복 요청을 줄일 수 있지만, 자동으로 서버 부하를 줄여주는 것은 아니다. 특히 쿠키는 관련 요청마다 서버로 함께 전송되므로 너무 많은 데이터를 담으면 오히려 요청 크기가 커진다.
LocalStorage에는 테마나 언어 같은 오래 유지할 설정을, SessionStorage에는 작성 중인 폼이나 탭별 진행 상태 같은 임시 데이터를 저장하기 좋다. 인증 토큰처럼 민감한 정보는 XSS 위험을 고려해야 하므로 무조건 LocalStorage에 넣기보다 서버 세션과 HttpOnly 쿠키 사용을 우선 검토하는 편이 좋다.
쿠키를 사용한다는 이유만으로 모든 서비스에 동일한 형태의 허용 팝업이 무조건 필요한 것은 아니다. 로그인 유지에 꼭 필요한 쿠키인지, 분석이나 맞춤형 광고에 사용하는 쿠키인지에 따라 요구사항이 달라질 수 있다.
개인정보나 행태정보를 수집한다면 수집 목적, 항목, 보유 기간, 거부 방법 등을 개인정보 처리방침에 명확히 안내해야 한다. 별도의 동의가 필요한 쿠키라면 동의를 받기 전에 비필수 쿠키를 저장하지 않고, 이용자가 거부하거나 나중에 철회할 수 있도록 구성해야 한다.
실제 서비스를 출시할 때는 서비스 대상 국가와 쿠키 사용 목적을 기준으로 개인정보보호위원회와 KISA의 최신 지침을 확인하는 것이 좋다.