브라우저 쿠키 속성 알아보기

JaeKyung Hwang·2024년 7월 18일
3

TECH

목록 보기
4/16
post-thumbnail

🍪쿠키(Cookie)

서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각

  • 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송
  • 데이터 저장 위치 = 웹 브라우저

사용 목적

  • 세션 관리(Session management)
    : 서버에 저장해야 할 로그인, 장바구니, 게임 스코어 등의 정보 관리
  • 개인화(Personalization)
    : 사용자 선호, 테마 등의 세팅
  • 트래킹(Tracking)
    : 사용자 행동을 기록하고 분석하는 용도

사용 방법

  • 서버 → 클라이언트: Set-Cookie header 사용
    클라이언트에게 쿠키를 저장하라고 전달
    Set-Cookie: <cookie-name>=<cookie-value>
  • 클라이언트 → 서버: Cookie header 사용
    Cookie: name=value; name2=value2; name3=value3

✨쿠키 속성(Cookie Attributes)

⏲️수명 관련

Expires

: 날짜 타임스탬프로 지정할 수 있는 쿠키의 최대 생명주기

Set-Cookie: id=1a2b3c4d; Expires=Thu, 31 Oct 2021 07:28:00 GMT;

Max-Age

: 쿠키가 만료할 때까지의 시간(초)

Set-Cookie: id=1a2b3c4d; Max-Age=2592000
  • 0 또는 음수 값일 경우 쿠키를 즉시 만료시킴
  • Expires 속성보다 우선 적용됨
  • 세션 쿠키(Session cookies)
    • Expires, Max-Age 속성이 없는 쿠키로 현재 세션이 끝날 때 삭제된다.
    • 브라우저는 "현재 세션"이 끝나는 시점을 정의하며, 어떤 브라우저들은 재시작할 때 세션을 복원해 세션 쿠키가 무기한 존재할 수 있도록 한다.
  • 영속 쿠키(Permanent cookies)
    • Expires 속성에 명시된 날짜에 삭제되거나, Max-Age 속성에 명시된 기간 이후에 삭제된다.
    • 쿠키가 저장되는 "클라이언트"의 시간 기준

🔐보안 관련

HttpOnly

: Cross-Site Scripting (XSS) 공격 방지하기 위해 JavaScript의 Document.cookie API에 접근할 수 없도록 하는 속성

Secure

: "HTTPS 프로토콜"을 사용한 요청일 때만 쿠키가 전송되도록 하는 속성

Domain

: 쿠키가 전송되게 할 host(server domain) 명시

  • 이 속성을 명시하지 않을 경우 현재 host가 기본 값으로 설정되고 하위 도메인(subdomain)을 포함하지 않는다.
  • 속성을 명시하면 하위 도메인(subdomain)까지 포함한다.

SameSite

: Site 간 요청과 함께 쿠키가 전송될지를 제어하여 Cross-Site Request Forgery (CSRF) 공격 방지하기 위한 방법을 제공하는 속성

  • chromium의 설명을 참고하면 다음과 같다.

    The SameSite attribute

    The SameSite attribute of a cookie specifies whether the cookie should be restricted to a first-party or same-site context. Several values of SameSite are allowed:

    • A cookie with "SameSite=Strict" will only be sent with a same-site request.
    • A cookie with "SameSite=Lax" will be sent with a same-site request, or a cross-site top-level navigation with a "safe" HTTP method.
    • A cookie with "SameSite=None" will be sent with both same-site and cross-site requests.

    Legacy SameSite behavior

    As of Chrome 80 (see launch timeline), a cookie that does not explicitly specify a SameSite attribute will be treated as if it were "SameSite=Lax". In addition, any cookie that specifies "SameSite=None" must also have the Secure attribute. (See https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.5 for the definition of the Secure attribute.)

    • SameSite 속성을 별도로 설정하지 않으면 default로 Lax이다.
      Lax는 same-site 요청일 때에는 쿠키가 전송되고, 이 외에는 Top Level Navigation(웹 페이지 이동)과, "안전한" HTTP 메서드 요청(GET, HEAD, OPTIONS, TRACE와 같이 상태를 변경하지 않는 요청)의 경우 전송된다.
    • cross-site 요청에서도 쿠키를 보내도록 하기 위해서는 SameSite 속성을 None으로 설정해야 하며 이 경우 https 통신을 할 때만 쿠키를 전송하도록 하는 Secure 속성이 반드시 true여야 한다.

🤨same-site? cross-site?

  • Site는 웹 페이지의 모음으로, 동일한 도메인에서 제공되고 단일 조직에 의해 유지관리된다.
  • 등록 가능한 도메인 (Registrable Domain)

    : 공개 접미사 목록(Public Suffix List)의 항목과 바로 앞의 도메인 네임 부분으로 구성
    • ex) same-site
      https://developer.mozilla.org/en-US/docs/
      https://support.mozilla.org/en-US/
    • ex) cross-site
      https://developer.mozilla.org/en-US/docs/
      https://example.com
  • Schemeful Same-Site

    : Scheme(protocol)이 다를 경우 다른 site로 간주됨
    • ex) cross-site
      http://example.com
      https://example.com
  • Port는 무관

    • ex) same-site
      https://example.com:8080
      https://example.com
  • same-site: 동일한 등록 가능한 도메인을 공유하며, Scheme(protocol)이 동일한 경우.
  • cross-site: 다른 등록 가능한 도메인을 가지거나, Scheme(protocol)이 다를 경우.
  • same-site인지 cross-site인지 아직도 헷갈린다면 Sec-Fetch-Site request header 확인!

    • 크롬 개발자 도구(F12) > Network > 요청 선택 > Headers 탭의 Request Headers에서 Sec-Fetch-Site를 확인하면 다음 4가지 값 중 하나를 가진다.
      Sec-Fetch-Site: cross-site
      Sec-Fetch-Site: same-origin
      Sec-Fetch-Site: same-site
      Sec-Fetch-Site: none
    • 이 외에도 Request Headers에 Origin, Referer 등의 유용한 정보가 담겨 있다.

🚀적용해보기

Auth API를 구현할 때 Cookie를 사용했다. 👉 API 별 flow 참고

댕댕워크 프로젝트를 진행하면서 프로그래머스 데브 코스 수료 이후 AWS 지원이 종료되면서 서버를 이전해야 할 일이 생겼다. 서버 이전을 하기 전 환경은 다음과 같았다.

environmentclient(frontend)server(backend)
localhttp://localhost:3000http://localhost:3333
prodhttps://dangdang-walk.vercel.apphttps://dangdang-walk.prgms-fullcycle.com

배포 환경이 cross-site였기 때문에 쿠키 속성은 다음과 같이 SameSite=None, Secure를 사용했었다.

private readonly cookieOptions: CookieOptions = {
    httpOnly: true,
    sameSite: this.isProduction ? 'none' : 'lax',
    secure: this.isProduction,};

브라우저에서 크로스 사이트 추적 방지 동작 때문에 서드 파티 쿠키가 전송되지 않는 문제가 있었기 때문에 서버를 이전하면서 아예 same-site로 변경하기로 했고 클라이언트와 서버의 도메인을 맞춰주기 위해 porkbun에서 도메인(https://dangdang-walk.xyz)을 구매했다.
(공개 접미사 목록에 xyz가 존재하는 것을 확인할 수 있다.)

Same site로 만들기 위해서는 다음과 같이 두 가지 방법이 있었고 회의를 거친 결과 간단히 subdomain을 사용하기로 결정했다.

  1. subdomain 사용

    • frontend: https://dangdang-walk.xyz
    • backend: https://api.dangdang-walk.xyz
  2. nginx를 이용해 port forwarding

    • frontend: https://dangdang-walk.xyz/3000
    • backend: https://dangdang-walk.xyz/3333

그래서 서버 이전 후 환경은 다음과 같이 same-site로 변경되었다.

environmentclient(frontend)server(backend)
localhttp://localhost:3000http://localhost:3333
prodhttps://dangdang-walk.xyzhttps://api.dangdang-walk.xyz

Sec-Fetch-Site request header를 보면 same-site인 것을 확인할 수 있다.

Cookie 설정은 Interceptor를 통해 처리해주었다. (코드 참고)

@Injectable()
export class CookieInterceptor implements NestInterceptor {
    constructor(
        private configService: ConfigService,
        private logger: WinstonLoggerService,
    ) {}

    private readonly isProduction = this.configService.get<string>('NODE_ENV') === 'prod';

    private readonly sessionCookieOptions: CookieOptions = {
        httpOnly: true,
        secure: this.isProduction,
        sameSite: 'lax',
    };

    private readonly refreshCookieOptions: CookieOptions = {
        ...this.sessionCookieOptions,
        maxAge: TokenService.TOKEN_LIFETIME_MAP.refresh.maxAge,
    };
  • XSS 공격 방지를 위해 HttpOnly 속성을 사용하고, Secure 속성은 배포 환경에서만 사용한다.
  • 이제 same-site 환경이므로 SameSite 속성은 기본 값인 Lax를 사용한다.
    CSRF 공격으로부터 어느 정도 보호받을 수 있다.
  • OAuth 관련 cookie는 따로 수명을 지정해주지 않은 채 세션 쿠키로 사용하였고, 발급한 refresh token을 cookie로 저장할 때만 maxAge를 지정해주었다.
    • OAuth 관련 cookie
    • Refresh token cookie
profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글