이번 프로젝트에서는 인증 방식으로 JWT 토큰 베이스 인증을 사용하기로 했고, 이를 위해 refresh-token과 access-token 두 가지를 사용하기로 했다. refresh-token은 쿠키에 http-only로 담고, access-token은 요청 헤더에 담기 위해 local storage 등 브라우저 저장소에 담기로 했다.
문제는 서버와 웹 개발 환경의 도메인이 다르다는 점이다. 서버는 AWS EC2에 띄우고 있지만, 웹 프론트엔드는 개발하는 동안 localhost 환경에서 EC2의 서버와 소통해야 하기 때문이다. 만약에 웹을 배포하더라도 프론트엔드와 서버의 도메인이 달라질 것 같다.
cross-domain에서는 CORS 정책으로 인해서 인증이나 쿠키같은 민감한 정보의 교환이 까다롭다. 이번에는 다른 도메인에 쿠키를 설정해 주는 부분에서 애를 먹었다.
CORS를 해결하고 cross-domain에 쿠키를 설정해 주려면 웹 프론트와 서버 양 측에서 설정을 올바르게 해 주어야 한다.
웹 프론트엔드에서 해야 할 일
credentials
설정에include
값을 주어야 한다. Axios의 경우withCredentials: true
옵션을 주면 된다.- 서버가 https를 당장 지원할 수 없는 상황이라면 ssl 무시 처리가 임시로 필요할 수도 있다. 이 부분은 아래에서 알아본다.
credentials
와 preflight
credentials
설정의 기본값은 same-origin
인데, 즉 같은 origin과의 통신에서만 쿠키나 인증 정보를 담겠다는 뜻이다. origin이 같으려면 Protocol(Scheme), Host(Domain), Port가 모두 같아야 한다.
include
설정값은 origin이 다르더라도 모든 요청에 인증 정보를 담겠다는 뜻이다. 대신에 이 값을 쓰려면 한 가지 조건이 추가되는데, 서버에서 응답의 Access-Control-Allow-Origin
헤더에 와일드카드가 아닌 명시적인 URL을 사용해야 하며 그 값이 요청 헤더의 origin 값과 일치해야 한다는 것이다.
보통의 요청에는 본 요청 이전에 preflight라는 예비 요청이 전송된다. 이 요청은 Options 메소드로 전송되며, Access-Control-Request-Headers
Access-Control-Request-Method
헤더를 포함해서 본 요청의 헤더와 HTTP 메소드 정보를 담는다. 그러면 서버는 Access-Control-Allow-*
헤더로 서버의 CORS 정책이 담긴 응답을 보낸다. 브라우저는 요청과 응답을 비교해서 클라이언트의 CORS 정책에 부합하는지 확인하고, 부합한다면 본 요청을 제대로 보내고 아니라면 본 요청을 보내지 않고 에러를 낸다.
스크린샷은 브라우저가 preflight 응답이 마음에 들지 않아서 에러를 낸 모습이다. 본 요청은 보내지지도 않고 CORS error라는 코드 값을 표시한다. 이런 에러가 나면 서버에서 처리를 해주어야 한다.
서버에서 해야 할 일
Access-Control-Allow-Origin
값에 명시적 URL을 등록한다.
- 와일드카드
*
를 쓰면 안된다. 와일드카드는 위의 preflight 에러의 가장 큰 원인이다. whitelist 등을 등록해서 요청의 origin에 따라 적절한Access-Control-Allow-Origin
을 설정하도록 한다.
Access-Control-Allow-Methods
헤더 값에Options
메소드가 없어서 preflight 요청이 전부 막혔던 적도 있다. 서버에서 Options 메소드를 적절히 처리하고 있는지도 확인해야 한다.- 응답의
Set-cookie
헤더의 쿠키 설정을 올바르게 한다.Samesite=None; Secure
설정이 되어 있는지 확인한다.
- SameSite 속성은 세 가지의 값을 가질 수 있다.
None
값은 도메인이 달라도 쿠키 전송을 가능하게 한다.Strict
는 같은 도메인에서만 쿠키 전송이 가능하고, 도메인이 다르면 전송이 불가능하다.Lax
는 기본값으로, Strict 설정에서 약간의 예외를 준다.a href, link href
와 같은 일부 경우에만 쿠키를 허용한다. - Secure 설정을 사용하려면
https
를 사용해야 한다. 이 부분은 테스트를 해보질 않아서 모르겠는데, 브라우저의 ssl 인증을 무시하는 몇 가지 방법으로 https를 사용하지 않고도 Secure를 사용할 수 있지 않을까...? 그런데 일단 아래 방법은 임시 방편이고, 가장 올바른 방법은 서버에 도메인과 ssl 인증서를 붙여서 https를 지원하는 것이다.
프론트엔드에서 SSL 인증서 무시하기
만약에 아래 방법을 사용한다 하더라도 개발 환경에서만 임시로 켜는 것이 좋을 것 같다.
axios
의 httpsAgent 옵션에rejectUnauthorized: false
를 추가
1 import https from "https";2 axios.defaults.httpsAgent = new https.Agent({3 rejectUnauthorized: false4 });
https.globalAgent.options.rejectUnauthorized = false;
package.json
에서 scripts 변경 -"dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0 && npx next dev
- Next.js 사용 기준이므로
next dev
부분에 프로젝트 개발 환경 실행 커맨드를 입력
쿠키가 설정되었는지 확인하기
~~어제 삽질의 가장 큰 원인..~~
원래 브라우저 개발자 도구의 Application 탭의 쿠키 스토리지에서 쿠키가 설정된 것을 확인할 수 있어야 한다. 그런데 위에서 저걸.. 다 했는데도 개발자 도구에서는 쿠키를 볼 수 없다. 그래서 쿠키가 설정되지 않은 줄 알았는데 사실 쿠키는 설정이 되어 있었다.. 개발자 도구에서 볼 수 없었던 이유는 쿠키의 도메인이 서버 도메인으로 설정되어 있었기 때문이다.
다른 도메인의 쿠키를 보고 싶다면
- Mac 크롬 기준으로 옵션 -> 설정 -> 개인정보 및 보안 -> 쿠키 및 기타 사이트 데이터 -> 모든 쿠키 및 사이트 데이터 -> 쿠키 검색에 서버 도메인 이름을 검색
- 더 쉽게 확인하려면, 서버 도메인 url로 들어가서 개발자 도구를 연다.
잘 있다... 잘 들어가 있었다... refresh 요청도 잘 된다... refresh-token의 경우에는 클라이언트 쪽에서 열어봐야 할 일이 없기 때문에 이렇게 저장되어 있어도 될 듯 하다.