쿠키와 인증

김동현·2023년 5월 30일
0

nodeJS

목록 보기
10/14

쿠키는 HTTP 프로토콜에 포함되어있는 기술 중 하나이다.
쿠키는 주로 세 가지 목적을 위해 사용된다.

  • 세션 관리(Session management)
    서버에 저장해야 할 로그인, 장바구니, 게임 스코어 등의 정보 관리

  • 개인화(Personalization)
    사용자 선호, 테마 등의 세팅

  • 트래킹(Tracking)
    사용자 행동을 기록하고 분석하는 용도

쿠키의 생성

const http = require("node:http");

const server = http.createServer((request, response) => {
  response.writeHead(200, {
    "Set-Cookie": ["yummy_cookie=choco", "tasty_cookie=strawberry"],
  });
  response.end("<h1>Cookie</h1>");
});
server.listen(3000, () => {
  console.log("http://localhost:3000");
});

쿠키의 읽기

브라우저의 개발자도구로 HTTP request를 보자.

브라우저에 저장된 쿠키값은 Cookie라는 이름의 헤더에 포함되어 서버로 전송한다.

const http = require("node:http");

const server = http.createServer((request, response) => {
  console.log(request.headers.cookie);
  response.writeHead(200, {
    "Set-Cookie": ["yummy_cookie=choco", "tasty_cookie=strawberry"],
  });
  response.end("<h1>Cookie</h1>");
});
server.listen(3000, () => {
  console.log("http://localhost:3000");
});

request.headers.cookie 로 브라우저가 보내는 쿠키에 접근이 가능하다.
출력결과는 다음과 같이 문자열로 나온다.
yummy_cookie=choco; tasty_cookie=strawberry
이 문자열들을 파싱해서 객체로 만들어서 사용해보자.
직접 코드를 짜도 되지만 cookie 라는 외부 모듈을 이용하기로 한다.

const http = require("node:http");
const cookie = require("cookie");
const server = http.createServer((request, response) => {
  const cookies = cookie.parse(request.headers.cookie ?? "");
  console.log(cookies.yummy_cookie);
  response.writeHead(200, {
    "Set-Cookie": ["yummy_cookie=choco", "tasty_cookie=strawberry"],
  });
  response.end("<h1>Cookie</h1>");
});
server.listen(3000, () => {
  console.log("http://localhost:3000");
});

쿠키의 활용

여러 언어를 지원하는 사이트일 경우, 쿠키로 사용자의 언어를 지정해서 서버에 넘겨주면 해당 언어에 맞는 페이지를 response할 수 있다.

또한 로그인을 하면 서버가 사용자를 식별할 수 있는 값을 쿠키에 담아 클라이언트로 response하는데, 이 값을 브라우저에 저장해서 request할 때마다 이 값을 서버에 전달하여 로그인을 두 번 하지 않고도 사용자를 식별할 수 있게 한다.

로그인을 하니, auth-cookie 라는 키를 가진 쿠키가 생성된 것을 확인할 수 있다.
앞으로 이 쿠키의 값으로 사용자를 식별한다.

만약 저 값을 복사한 뒤, 로그아웃을 하고 수동으로 쿠키를 생성해서 서버에 전송한다면 어떻게 될까?
내 계정으로 로그인이 된 상태로 변경된다.

Session cookies, Permanent cookies

Session cookies는 현재 세션이 끝날 때 브라우저에서 삭제된다.
세션이 끝나는 시기는 브라우저가 결정한다.
보통의 경우 브라우저를 꺼버리면 세션이 종료된다.
하지만 일부 브라우저는 재시작할 때 세션을 복원시키는 경우도 있다.
이로 인해 Session cookies 가 무기한으로 유지될 수도 있다.

Permanent cookies는 특정 시간에 삭제된다.
시간을 특정하는 두 가지 속성이 있다.
ExpiresMax-Age이다.
둘 중 하나만 선택하여 사용하면 되고, Expires는 쿠키가 삭제되는 특정 시기를 뜻하고, Max-Age는 브라우저에 쿠키가 저장된 이후로 몇초간 유지될 것인가를 뜻한다.
설정하는 문법형식은 다음과 같다.
Set-Cookie: id=a3fWa; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
코드 예제로 살펴보자.
아래의 코드에서는 Expires 대신에 Max-Age 를 사용했다.

response.writeHead(200, {
  "Set-Cookie": [
    "yummy_cookie=choco",
    "tasty_cookie=strawberry",
    `Permanent_cookies=orange;Max-Age=${10};`,
  ],
});


tasty_cookieyummy_cookie 쿠키는 Session 쿠키로 되어있는 반면 Permanent_cookies 는 날짜가 보인다.
10초뒤에 사라지는 모습도 볼 수 있다.

Secure, HttpOnly

사용자를 식별하는 쿠키와 같은 경우엔 탈취당하면 큰일난다.
그래서 쿠키를 지킬 수 있는 두 가지 방법이 있다.
SecureHttpOnly 속성이다.
Secure 는 HTTPS 프로토콜에서만 서버로 전송한다.

response.writeHead(200, {
  "Set-Cookie": [
    "yummy_cookie=choco",
    "tasty_cookie=strawberry",
    `Secure_cookies=secure;Secure;`,
  ],
});


쿠키를 서버로부터 받아와서 브라우저에 저장이 되었다.

하지만 서버에 Request 할 땐 Secure 쿠키가 전송되지 않는다는 것을 확인할 수 있다.
프로토콜이 HTTPS 가 아닌 HTTP 이기 때문이다.

HttpOnly 속성은 해당 쿠키를 자바스크립트로 조작할 수 없도록 한다.

response.writeHead(200, {
  "Set-Cookie": [
    "yummy_cookie=choco",
    "tasty_cookie=strawberry",
    `http_only=http_only;HttpOnly;`,
  ],
});


위의 이미지를 보면 분명 브라우저엔 http_only 쿠키가 저장되어 있지만 자바스크립트로 확인해보면 제외되어 있는 것을 볼 수 있다.

Domain

Domain 은 쿠키를 수신할 수 있는 호스트를 지정한다.

Domain 을 지정하지 않은 쿠키의 경우를 보자.

response.writeHead(200, {
  "Set-Cookie": [
    `domain=domain`,
  ],
});

sub.localhost 에 접속해보자.

sub.localhost 와 같은 도메인을 localhost 도메인의 서브 도메인 이라고 부른다.


브라우저에 쿠키가 저장되어서 Domain란에 sub.localhost 라고 표시된 것을 확인할 수 있다.
이제 서버에서 쿠키를 보내주는 위의 코드를 주석처리하여 더 이상 서버에서 쿠키를 보내는 작업을 못하도록 해보자.
이렇게 하는 이유는 위에서 받아온 쿠키의 동작을 살펴보기 위함이다.

sub.localhost 의 하위 도메인인 test.sub.localhost 로 접속해보자.

쿠키가 없다.
sub.localhost 의 상위 도메인인 localhost 로 접속해보자.

역시나 쿠키가 없다.

정리해보자.
Domain 을 생략한 경우 쿠키는 다음과 같이 동작한다.

  • 쿠키를 받아온 도메인을 제외한 다른 도메인(상위 및 하위)에서의 공유가 불가능하다.

Domainsub.localhost 로 지정해보자.

response.writeHead(200, {
  "Set-Cookie": [`domain=domain;Domain=sub.localhost;`],
});


당연히 해당 도메인으로 접속할 때는 쿠키를 잘 받아온다.
자세히 살펴보면 Domain 란에 sub.localhost 가 아닌 .sub.localhost 로 세팅되어있는걸 볼 수 있다.
이제 서버에서 쿠키를 보내는 코드를 주석처리후,
sub.localhost 의 하위 도메인인 test.sub.localhost 로 접속해보자.

하위 도메인으로 공유가 된다.
상위 도메인인 localhost 로 접속해보자.

쿠키가 공유되지 않는다.

정리해보자.
Domain 을 지정한 경우 쿠키는 다음과 같이 동작한다.

  • 지정한 도메인의 하위 도메인에는 쿠키를 공유한다.
  • 지정한 도메인의 상위 도메인에는 쿠키를 공유하지 않는다.

Path

Path 속성은 Domain 속성과 비슷하다.
단지 도메인과 경로의 차이다.
Path 를 지정하면 지정한 경로를 포함한 하위 경로로는 쿠키가 공유되고 상위 경로로는 공유되지 않는다.
생략하면 기본값으로 / 로 지정된다.

쿠키 삭제

response.writeHead(200, {
  "Set-Cookie": [`delete=delete`],
});

쿠키를 먼저 만들어주고 브라우저에서 확인해보자.

Set-Cookie 에 빈 값을 넣어줘서 쿠키를 없앨 수 있지만, 혹시나 쿠키가 살아 있을 수 도 있으며 어떤 쿠키를 삭제시켰는지 한 눈에 알아보기 어렵다.
따라서 삭제할 쿠키는 Max-age 를 이용해서 다음과 같이 작성한다.

response.writeHead(200, {
  "Set-Cookie": [`delete=delete;Max-Age=0;`],
});

SameSite

SameSite 속성은 CSRF 공격을 방지하기 위해 사용된다.
다음의 3가지 값중 하나를 가지며, 기본값은 Lax 이다.

  • None: 이 값을 설정하면 쿠키가 항상 전송된다.
    이는 쿠키가 같은 사이트뿐만 아니라 다른 사이트로도 전송될 수 있음을 의미한다.
    그러나 Secure 속성이 설정되어 있어야 하며, HTTPS 프로토콜을 통해 전송되어야 한다.

  • Lax: 이 값은 기본값이며, 대부분의 일반적인 시나리오에 사용된다.
    SameSite=Lax로 설정된 쿠키는 third-party 요청과 함께 전송되지 않는다.
    즉, 사용자가 링크를 클릭하여 다른 사이트로 이동하는 경우에는 쿠키가 전송되지 않는다.
    그러나 폼 제출과 같은 third-party 요청을 통해 서버로 전송되는 경우에는 쿠키가 전송된다.

  • Strict: 이 값은 쿠키가 항상 third-party 요청과 함께 전송되지 않음을 의미한다.
    쿠키는 오직 현재 사이트의 요청과 함께만 전송된다.
    이는 가장 엄격한 설정이며, CSRF 공격을 방지하는 데 가장 유용하다.
    그러나 주의해야 할 점은, SameSite=Strict로 설정된 쿠키는 일부 브라우저에서는 서버 간 리디렉션 시 쿠키가 전송되지 않을 수 있다는 것이다.
    따라서 브라우저 호환성을 체크해야한다.

LaxStrict 의 차이점을 좀 더 알기 쉽게 예시를 들어보자.

현재 도메인이 "example.com"이고, 로그인 상태를 유지하기 위해 SameSite 속성이 적용된 세션 쿠키를 사용하고 있다고 가정해보자.

  • SameSite=Lax: 사용자가 "example.com"에서 로그인한 후, 다른 도메인인 "external.com"으로 이동하는 링크를 클릭하는 경우, 세션 쿠키는 전송되지 않는다.
    사용자의 인증 상태는 "external.com"에서는 유지되지 않는다.
    그러나 "external.com"에서 폼을 제출하거나 AJAX 요청을 통해 다시 "example.com"으로 이동하는 경우, 세션 쿠키는 전송된다.
    사용자의 인증 상태는 이러한 요청에서는 유지됩니다.

  • SameSite=Strict: 사용자가 "example.com"에서 로그인한 후, 다른 도메인인 "external.com"으로 이동하는 링크를 클릭하는 경우에도 세션 쿠키는 전송되지 않는다.
    인증 상태가 외부 도메인으로 전달되지 않는다.
    "external.com"에서 "example.com"으로 폼을 제출하거나 AJAX 요청을 통해 다시 "example.com"으로 이동하는 경우에도 세션 쿠키는 전송되지 않는다.
    인증 상태는 이러한 요청에서도 유지되지 않는다.

따라서, Lax는 Strict보다 유연한 설정이다.
Strict는 가장 엄격한 설정이며, 외부 도메인과의 모든 상호작용에서 쿠키 전송을 차단하여 CSRF 공격을 방지한다.

Cookie prefixes

"example.com" 도메인과 그 아래의 "sub.example.com" 서브도메인이 있다고 가정해보자.

  • 취약한 응용 프로그램과 쿠키 설정: "sub.example.com"에서 동작하는 취약한 응용 프로그램은 Domain 속성을 가진 쿠키를 설정할 수 있다.
    예를 들어, 도메인이 ".example.com"인 쿠키를 설정할 수 있다.
    이 취약한 응용 프로그램은 로그인할 때 "sub.example.com" 도메인에 쿠키를 설정하고, 이 쿠키를 가지고 "example.com"의 다른 서브도메인에서도 접근할 수 있다.

  • 세션 고정 공격 (Session Fixation Attack): 악의적인 공격자가 사용자에게 "sub.example.com" 도메인에 방문하도록 유도한다.
    이 공격자는 같은 도메인에 대한 로그인 쿠키를 설정해 놓는다.
    사용자는 "sub.example.com"에 로그인하고, 악의적인 공격자가 설정해놓은 세션 쿠키를 받는다.
    사용자가 "example.com"의 다른 서브도메인으로 이동하면서 이 세션 쿠키를 사용하면, 악의적인 공격자가 해당 세션을 조작하여 사용자의 계정을 악용할 수 있다.

이러한 공격을 방지하기 위해 Cookie prefix 인 __Host-__Secure- 를 사용할 수 있다.

  • __Host-: 쿠키 이름에 이 접두사가 있는 경우, 해당 쿠키는 Set-Cookie 헤더에서만 설정할 수 있다.
    해당 쿠키는 반드시 Secure 속성이 설정되어 있어야 하며, 안전한 출처에서 전송되어야 한다.
    Domain 속성을 포함하지 않아야 하고, Path 속성이 "/"로 설정되어 있어야 한다.
    이러한 조건을 충족하지 않는 쿠키는 브라우저에 의해 거부된다.

  • __Secure-: 쿠키 이름에 이 접두사가 있는 경우, 해당 쿠키는 Set-Cookie 헤더에서만 설정할 수 있다.
    해당 쿠키는 반드시 Secure 속성이 설정되어 있어야 하며, 안전한 출처에서 전송되어야 한다.
    __Host- 접두사보다 약한 보안 수준을 가진다.

[참고] : 유튜버 생활코딩
[참고] : MDN

profile
프론트에_가까운_풀스택_개발자

0개의 댓글