CORS와 cookie sameSite

2025. 1. 20. 22:59·Web/Web 전반

 

사용자 인증 기능을 개발하다가 CORS와 coockie 때문에 고생했던 경험이 있었다.

이에 대한 정의와 목적은 MDN이나 web.dev에 잘 설명되어 있으니 여기서는 간단한 개념 정리와 예시(백엔드 express.js와 프론트엔드에서 구현)와 세션쿠키를 사용할 때 주의점에 대해서 정리해보려 한다.

 

먼저 아래의 질문을 모르겠다면 공식 문서를 다시 읽어보면 좋을 것 같다.

브라우저의 접속 주소(https://192.168.100.4:3000), 웹 서버 주소(http://192.168.100.4:4000) 인 경우 

same-origin / cross-ogirin

same-site / cross-site 

각각 무엇일까?

 

답:

더보기

cross-origin, same-site

 

문제가 됐던 경우는 위와 같은 환경에서 쿠키를 사용하여 사용자 인증을 하는 경우, 쿠키의 sameSite 속성이 lax, strict 여도 쿠키 전달이 잘 될 것이다.

그러나 배포하는 과정에서 cross-stie가 되어버리면, 쿠키전달이 안될 것이다.

예를 들어, vercel에 프론트엔드를, aws에 백엔드를 배포하는 경우다. (별다른 설정을 하지 않았을 경우)

이런 경우 sameSite를 none으로 설정하거나

프론트엔드와 백엔드 앞단에 프록시 서버를 띄우거나

등등 해결할 수 있는 방법은 많지만 crossSite라서 쿠키 전달이 안되는 걸 까먹지말자

 

CORS(Cross-Origin Resource Sharing)

먼저 origin은 url에서 아래 그림과 같다.

출처: https://docs.tosspayments.com/blog/payment-window-cors-error

 

아래 표를 보고 same-origin과 cross-origin을 헷갈리지 말자

출처: https://web.dev/articles/same-site-same-origin#"schemeful-same-site"

CORS는 브라우저가 자신의 origin이 아닌 다른 origin에서 자원(resource)를 서버가 로딩하는 것을 허용하도록 서버가 허가해주는 HTTP 헤더 기반 매커니즘이라고 한다는데 참 어려운 문장이다.

간단하게 말하면 domain-a.com(브라우저)에서 domain-b.com(백엔드)에 HTTP 요청을 할 수 없다.

 

예시를 보면 이해하기 쉬울 것 같다.

아래 그림을 보면 웹 페이지는 domain-a.com에서 호스팅 되었고, image는 domain-a.com (same-origin)에 요청하고 Canvas관련 데이터는 domain-b.com(cross-origin)에 요청한다.

만약 CORS 설정이 안되어있다면 Canvas 관련 데이터를 가져오지 못할 것이다.

출처: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

CORS를 피하기 위해서는 아래와 같은 두 가지 방법이 있는데 1번에 대해서만 작성해보려 한다.

 

1. 백엔드에서 Access-Control-Allow-Ogirin 헤더 설정

2.브라우저에서 접속할 프록시 서버 사용

프록시 서버를 경유할 경우 브라우저에서는 CORS를 피할수 있고, 프록시 서버의 경우는 브라우저가 아니므로 CORS 정책에 위배되지 않음 ( CORS는 브라우저 정책)

https://create-react-app.dev/docs/proxying-api-requests-in-development/

(react로 개발할 때 CORS 문제가 생기면 위 문서를 따라하면 되는 것으로 보임)

 

1번의 경우 아래의 케이스로 나뉠 것 같음

case #1) 모든 cross-origin에서 HTTP 요청이 가능하게 할 경우 

case #2) 특정 cross-origin에서만 요청이 가능하게 할 경우

case #3) HTTP 요청에 사용자 인증이 정보가 포함되어 있는 경우

 

구현

백엔드

case #1) 모든 cross-origin에서 HTTP 요청이 가능하게 할 경우 

Access-Control-Allow-Origin 헤더를 와일드카드(*)로 설정

Access-Control-Allow-Origin: * (모든 도메인에서의 요청을 허용)

 

case #2) 특정 cross-origin에서만 요청이 가능하게 할 경우

Access-Control-Allow-Origin 헤더에 특정 origin을 추가

Access-Control-Allow-Origin: https://www.example.com

 

case #3) HTTP 요청에 사용자 인증이 정보가 포함되어 있는 경우

Access-Control-Allow-Credentials 헤더를 true로 설정

Access-Control-Allow-Origin 헤더에 특정 origin을 추가 (Access-Control-Allow-Credentials가 true일 경우 * 사용 불가)

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true (쿠키, Authorization 헤더 등 인증정보 포함 여부)

 

 

백엔드에서 express를 사용한 구현 예시 (cors모듈 사용)

case #1 

const corsOptions = {
  origin : "*"
};
app.use(cors(corsOptions));
 

 

case #2

const corsOptions = {
  origin: ["http://localhost:3000", "http://192.168.219.104:3000"],
};
app.use(cors(corsOptions));
 

 

case #3

const corsOptions = {
  origin: ["http://localhost:3000", "http://192.168.219.104:3000"],
  credentials: true,
};
app.use(cors(corsOptions));

 

 

프론트엔드

#1, #2 케이스에서 프론트엔드가 할 일은 없고

#3 케이스에서처럼 HTTP 요청에 사용자 인증 정보를 포함해야 하는 경우 아래와 같이 fetch 옵션에 credentials: 'include' 추가

 

프론트엔드에서 구현 예시 (fetch)

 const response = await fetch("http://192.168.219.104:5000/api/test-get", {
    method: "GET",
    credentials: "include",
 });

 

CORS는 생각보다 생각할게 없는데, 사용자인증을 쿠키로 하는 경우에 조금 헷갈린다.

 


글을 적다보니 생각난건데, 구글 OAuth 사용할 때 승인된 Javascript 원본에 URI을 넣어야 했는데 OAuth 인증하는 서버에서 #3 케이스와 같은 CORS 설정이라 origin을 추가하기 위해 적었던 게 아닐까?

Cookie sameSite

스키마와 eTLD+1이 동일한 웹사이트는 '동일 사이트'로 간주됩니다. 스키마가 다르거나 eTLD+1이 다른 웹사이트는 '크로스 사이트'( 2019년 말부터 same-site에는 스키마(프로토콜)가 포함됨) 라고 하는데.. 뭔소린가 싶다.

아래 예시를 보면

TLD(Top-Level domain)은 .com이고 TLD+1은 example.com이다. 여기서 TLD+1과 scheme(프로토콜)이 같으면 same-site로 간주한다는 것이다.

(포트는 달라도 된다는 게 충격이다. 당연히 cross-site인줄 알았다.)

출처: https://web.dev/articles/same-site-same-origin

 

아래 표를 보고 cross-site 구분하면 될 것 같다.

출처: https://web.dev/articles/same-site-same-origin

 

 

쿠키의 same-site 속성

None

cross-site 에서도 쿠키 전달 가능하지만 쿠키의 secure 옵션을 true로 설정해야 함

ex)

https://domain-a.com (브라우저) -> https://domain-b.com (백엔드) (O)

http://domain-a.com (브라우저) -> http://domain-b.com (백엔드) (X)

 

Lax

cross-site 에서는 특정 방법으로만 쿠키 전달 가능

  • Top Level Navigation, 안전한 HTTP 메소드 요청(HEAD, OPTIONS,..)
  • (예를 들어 <a> 태그를 통한 이동, window.location.href을 사용하는 이동)
  • <iframe>, <img> 태그 등에서는 쿠키를 전송하지 않음
  • fetch와 XMLHttpRequest 요청에도 쿠키를 전달하지 않음

ex) 

https://domain-a.com (브라우저)에서 사용자가 <a href="https://domain-b/session">를 클릭하여 이동할 경우

domain-b의 쿠키가 백엔드로(https://domain-b) 전달됨

예를 들어, 사용자가 구글 검색을 통해 domain-b.com에 접속하게 될 경우 domain-b의 쿠키가 백엔드로 전달됨

만약 domain-b의 session cookie가 lax라면, 로그인이 되어있음

 

Strict

cross-site 에서는 쿠키를 전달하지 않음

ex) 사용자가 구글 검색을 통해 domain-b.com에 접속하게 될 경우 domain-b의 쿠키가 백엔드로 전달되지 않음

만약 domain-b의 session cookie가 strict라면, 로그인이 되어있지 않을 것이라고 예상할 수 있지만 domain-b.com 접속 요청 요청에는 쿠키가 없어도, 이후 프론트엔드에서 로드된 js에서의 요청에는 session cookie가 포함될 것이므로 로그인이 되어있을 수 있음

 

구현

백엔드 (express)

  res.cookie("example_cookie", "cookie_value", {
    sameSite: "none",   //lax or strict
    secure: true,       //lax or strict에서는 false도 가능
    maxAge: 24 * 60 * 60 * 1000,
    httpOnly: true      //클라이언트단에서 js로 접근 불가능
  });

 

프론드엔드

아래와 같이 fetch 옵션에 credentials: 'include' 추가

 const response = await fetch("http://192.168.219.104:5000/api/test-get", {
    method: "GET",
    credentials: "include",
 });

 

 

테스트

sameSite: "lax"일 때 아래와 같이  쿠키가 전달됨

 

sameSite: "strict"일 때 아래와 같이 쿠키가 전달되지 않음

 

 

sameSite: "strict"일 때, 웹 페이지 내의 js 코드에서 /api/session 요청으로 session 데이터를 받을 경우 아래와 같이 잘 나옴

 

이러면 대체 왜 lax와 strcit를 구분하는 걸까

cross-site에서의 첫 요청의 쿠키 전달 여부가 어떤 의미가 있는걸까

백엔드에서 정적 페이지를 만들어서 서빙하면 의미가 있을 것도 같은데 너무 옛날 방식이라..

 

--> 생각해보니 Next.js에서도 서버 사이드 렌더링을 하니 사용자 인증을 strict 쿠키로 하면 사용자 인증이 안되겠네

 

끝. 

 

 

 

 

 

 

 

 

 

'Web > Web 전반' 카테고리의 다른 글

[CSS] 유틸리티 클래스에 대하여  (0) 2025.03.09
express.js self signed SSL 인증서 적용  (0) 2025.02.27
open SSL을 이용한 self signed SSL 인증서 발급  (0) 2025.02.27
[날짜 관련] 트러블 슈팅  (1) 2025.02.09
DOM 용어에 대하여  (0) 2025.01.05
'Web/Web 전반' 카테고리의 다른 글
  • express.js self signed SSL 인증서 적용
  • open SSL을 이용한 self signed SSL 인증서 발급
  • [날짜 관련] 트러블 슈팅
  • DOM 용어에 대하여
Danny.Song
Danny.Song
  • Danny.Song
    일기장
    Danny.Song
  • 전체
    오늘
    어제
    • 분류 전체보기 (74)
      • Web (19)
        • React.js (4)
        • Next.js (1)
        • Node.js (3)
        • Web 전반 (10)
      • Unity Robotics (8)
      • ROS2 (7)
      • SW 이것저것 (6)
      • 일상 (2)
      • etc (4)
        • 프로젝트 (1)
        • 주식일기 (1)
        • Unity (1)
        • 하루 계획 (0)
        • 배움 (0)
        • 소프트웨어공학 (0)
        • 운영체제 (1)
        • etc... (0)
      • Java (26)
        • 프로젝트 (8)
        • cmd에서 자바실행 (2)
        • 통신 (3)
        • xml (6)
        • Database (3)
        • 코딩테스트 (0)
        • 디자인패턴 (1)
        • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    유저 인증 상태 관리
    nav2
    지뢰찾기
    goal_checker
    self signed ssl 인증서 발급
    ai navigation
    클로드 코드
    pointcloud_to_laserscan
    soft-navigation measure
    minesweeper
    ros2
    XML Schema
    node-alpine
    Dom
    amcl
    소프트 내비게이션 성능 측정
    Schema
    Unity
    java
    telegram chatbot
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Danny.Song
CORS와 cookie sameSite
상단으로

티스토리툴바