CORS의 Cookie

불꽃남자·2021년 7월 27일
13
post-thumbnail

서론

서버와 클라이언트의 통신을 이용한 로그인 기능을 구현하는 것에 쿠키를 사용하기로 했다.
왜냐하면 httpOnly 쿠키 헤더를 활성화하면 XSS 공격은 어느 정도 방어가 가능하고, 쿠키는 클라이언트에서 코드로 조작하는 등의 수고로움이 없기 때문이다.

그런데 CORS의 세계는 너무나 냉혹했다. CORS 정책을 따르지 않는 쿠키는 HTTP 통신을 할 수 없었다.
지금부터 3일 밤낮을 헤매고 4일째 아침에 찾은 답을 포스팅하겠다.

CORS Cookie

Same-Site 속성

일단 쿠키 헤더의 Same-Site 속성에 대해 알아보자.

Same-Site 속성은 다른 도메인 간의 통신에 대한 보안에 대한 설정이고, 3가지 설정값이 있다.

  1. Strict
    말 그대로 엄격하게 쿠키를 제한한다. 서로 다른 도메인간의 쿠키 교환을 절대 금지한다.
  2. Lax
    Strict보다 느슨하게 쿠키를 제한한다. 서로 다른 도메인간의 쿠키 교환이라 할지라도 top-level navagation 에서 일어나는 안전한 HTTP Method 일 때에는 쿠키를 허용한다.
    top level navigation은 브라우징 컨텍스트에 관한 것인데, 간단히 말하자면 해당 탭의 URL이 바뀌는 요청에 Lax 쿠키가 포함된다.
    이를태면 a태그와 link태그를 클릭했을 때 발생하는 GET 요청에 쿠키가 포함되고, 현재 탭의 URL을 바꾸진 않으나 form GET 요청에도 Lax 쿠키가 포함된다.
  3. None
    서로 다른 도메인간의 쿠키 교환을 제한하지 않는다. Same-Site가 None인 쿠키는 반드시 Secure가 true여야 한다.
    Secure 속성이 활성화되면 HTTPS 프로토콜 간의 통신에만 쿠키가 포함될 수 있다. 아마 Same-Site를 None으로 할 거면 최소한의 보안 수단으로 Secure 속성을 활성화하라는 의도인 것 같다.

그래서 발생하는 문제

내 프로젝트의 HTTP 통신에서 /login 요청은 POST이고, 요청의 응답에는 token이 쿠키로 포함되어야 한다. 그래서 Same-Site는 None이어야 하고, Secure를 활성화 한다.
하지만 개발을 하는 localhost는 기본적으로 HTTP 프로토콜을 사용하기 때문에, localhost의 프로토콜을 HTTPS로 바꿔야했다.

How to use HTTPS for local development 포스트를 참고함으로써 이 문제는 해결했다.

Credentials

두 번째는 CORS 통신에 포함 할 쿠키에 관한 설정이다. 쿠키는 요청을 할 때에 자동으로 요청에 포함되지만, CORS 요청에서는 아니다.

클라이언트

클라이언트의 XHR 객체에는 withCredentials이라는 옵션이 있다. (XHR.withCredentials 문서)
이는 쿠키, Authorization header 같은 user Credentials를 요청에 포함할 것인지에 대한 설정이다. true로 설정하면 user Credentials를 요청에 포함할 수 있다.

하지만 이대로만 요청을 보내면 Request error가 발생한다.

서버

에러 로그를 읽어보면 Credentials request에 대한 Response의 헤더에 Access-Control-Allow-Credentialstrue여야 한다고 한다.

나는 express를 사용하고 있으니 express 기준으로 설명하겠다.
express cors middleware 문서의 config option 문단을 보면 해당 옵션에 대한 설명이 있다.

const express = require("express");
const server = express();
const cors = require("cors");

...

server
  .use(cors({ origin: true, credentials: true }))
  .use(express.json())
  .use(cookieParser());

...

이렇게 설정해주면 해당 서버의 모든 응답에 Access-Control-Allow-Credentials 헤더가 붙는다.
cors의 config 객체를 보면 origin: true도 설정해줬는데, 이유는 다음과 같다.

Access-Control-Allow-Credentialstrue인 응답은 반드시 Access-Control-Allow-Origin이 명시되어 있어야 한다. 와일드카드( * )는 사용할 수 없고, origin을 명시해야한다.
cors cofing의 origintrue로 설정하면 요청을 보내온 origin이 Access-Control-Allow-Origin의 값으로 설정된다.

마침내

이 모든 문제가 해결되었다면, 이렇게 쿠키가 포함된 응답을 받을 수가 있다.
여기까지 내가 이 문제를 접한지 1일차에 해결한 부분이다.

그런데

그런데 나는 Chrome 개발자 도구의 Application 탭의 Cookies storage에는 저장된 쿠키가 안 보이는데, 왜 이러는지 모르겠다.

거기에 나는 쿠키가 필요한 API에 요청을 보냈는데, 자꾸 에러가 떴다.

서버에 요청은 제대로 갔는데, refreshToken 쿠키가 없다고 에러를 던지고 있다. 이것 때문에 3일 밤낮을 헤맸다.

그리고 해결방법은 어처구니 없게 간단했고, 나는 어처구니 없게 멍청했다.

진짜 해결

...

  const requestRefresh = async () => {
    const response = await axios.get(
      "쿠키가.필요한/API/EndPoint"
    );

    console.log(response);
  };

...

이게 쿠키가 필요한 API로 보내는 요청 코드였는데, 여기에도 withCredentials 옵션이 필요했다.

그러니까, 쿠키를 받을 요청 뿐만 아니라 쿠키를 보낼 요청 또한 withCredentials 옵션을 true로 설정해야한다!

아니, 지금 와서 생각해보면 분명히 이치에 맞는 말이다.
하지만 그 당시에는 Application의 Cookie Storage에 쿠키가 나타나지 않아서 쿠키를 제대로 못 받아와서 생긴 일이라고 생각했다. 그래서 해결이 더뎠다.

그러니까 위의 요청에 withCredentials 옵션을 true로 설정하면..

...

  const requestRefresh = async () => {
    const response = await axios.get(
      "쿠키가.필요한/API/EndPoint",
      {
      	withCredentials: true
      }
    );

    console.log(response);
  };

...


뭐야... 잘 되잖아...

요약

클라이언트

쿠키가 필요한 모든 요청에 withCertification 옵션 활성화( 쿠키를 받는 요청에도, 주는 요청에도 모두 )
https 프로토콜 사용해야 함 ( Secure 쿠키는 HTTPS 간의 통신에만 전송 돼서 )


서버

응답 Access-Control-Allow-Credentials 헤더 활성화
응답 Access-Control-Allow-Origin 헤더에 도메인 명시( 와일드 카드 사용 불가 )
쿠키 헤더에 Same-Site: "None" 설정( 다른 Origin간의 통신에도 전송되게 ) , Secure 설정( Same-Site가 None이면 Secure 활성화 되어야함 )

🐬

그래도 시간 들이고 해결 못 하는 것 보단 해결을 한 것이 낫다. 어쨌든 내가 이겼다.
그리고 오류를 해결하면서 HTTP 통신과 쿠키, 뭐 이런 것들에 대해서도 여러가지 배웠고, CORS 이슈에 대해 서버와 클라이언트 둘 다 관심을 기울여야 한다는 것을 느꼈다. 실제로도 그렇고.

나는 그냥 로그인 기능이 있는 CRUD 리액트 웹앱을 만들고 싶었는데, 생각 이상으로, 너무나도 생각 이상으로 알아야 할 것이 많다. 배우는 것이 많아서 좋기는 하다.

profile
프론트엔드 꿈나무, 탐구자.

4개의 댓글

comment-user-thumbnail
2022년 7월 5일

덕분에 잘 이해하고갑니다. 감사합니다 !!

답글 달기
comment-user-thumbnail
2022년 7월 31일

안녕하세요~
혹시 API 서버를 HTTPS로 해줘야하나요? 아니면 프론트엔드 실행하는 Dev Server를 HTTPS로 해줘야하나요??

1개의 답글
comment-user-thumbnail
2023년 10월 4일

ㅋㅋㅋㅋㅋㅋ 정대만짤 ㅋㅋㅋ 잘보고갑니다

답글 달기