front는 vue.js, back은 node.js를 사용하여 프로젝트를 진행하던 중 배포를 하면 쿠키 전달이 안 되는 오류가 발생했다.
배포는 둘 다 cloudtype에서 진행하였다.
로그인 로직은 passport 모듈을 활용하여 만들었고 쿠키와 세션을 동시에 쓰고 있었다.
구글에 검색하면 아마 이런저런 해결방법이 많이 나올 것이다.
vue와 node가 결국 배포를 하면 localhost가 아닌 다른 도메인이라서 CORS 설정을 따로 해야된다든지 아니면 쿠키 설정 시 도메인을 추가로 입력해야된다든지,,
하지만 나의 경우에는 설정을 다 해줬음에도 불구하고 쿠키가 여전히 찍히지 않는 상황이 발생했고 한 달 넘게 삽질한 결과 해결을 한 상태이다. 그래서 혹시 나와 같은 오류가 있는 사람들이 이 글을 보고 금방 해결하기를 바라며 해결 방법을 공유해보려고 한다.
우선 쿠키와 세션에 대해 조금 설명해보겠다.
Cookie란?
Cookie는 데이터이면서, 우리가 현재 사용하는 컴퓨터에 작은 텍스트파일로 저장되어있는 것
어떤 사이트에 접근을 하고 '하루 동안 이 창을 보지 않기'라는 문구를 본적이 있는가? 아니면, 로그인을 위해 로그인 정보
브라우저가 웹 페이지를 서버로부터 요청하게되면, 이 페이지에 속한 쿠키들이 추가로 요청된다. 이러한 방법을 통해 유저에 대한 정보를 기억하는 필수 데이터를 서버가 가져올 수 있는 것이다.
💡 쿠키와 세션의 차이는?
쿠키는 사용자가 브라우저의 쿠키데이터를 비우거나 금지하지 않는다면 계속해서 유지가 된다.
이와 달리 세션 저장소의 경우 방문자가 접속해 세션이 발생하고 지정된 시간이 지나 세션이 종료되면 저장소의 내용도 사라지게 된다는 점이 차이점이다.
쿠키 파라미터
① Name 속성과 Value 속성
- Name과 Value 속성은 데이터를 저장하고 읽는 데 사용하는 속성이다.
- 따라서 쿠키를 사용할 때는 Name과 Value 속성을 반드시 지정해야 한다.
② Expires 속성
- Expires 속성은 쿠키의 파기 날짜를 지정하는 속성이다.
- Expires 속성에는 GMT 형식이나 UTC 형식으로 날짜를 입력해야 한다.
- 파기 날짜를 입력하지 않으면 브라우저가 종료될 때 쿠키가 삭제된다.
③ max-age 속성
- expires 옵션의 대안으로, 쿠키 만료 기간을 설정할 수 있게 해준다.
- 현재부터 설정하고자 하는 만료일시까지의 시간을 초로 환산한 값을 설정한다.
④ Secure 속성
- Secure 속성을 지정하면 해당 쿠키는 SSL을 사용해서만 요청할 수 있다.
⑤ Domain 속성
- Domain 속성을 입력하지 않으면 현재 도메인의 경로로 자동 입력된다.
- 페이지 요청과 비교해서 도메인의 경로와 Domain 속성이 일치하지 않으면, 쿠키에 접근하는 것을 막으므로 Domain 속성은 건드리지 않는다.
⑥ Path 속성
- Path 속성을 입력하지 않으면 현재 도메인의 경로로 자동 입력된다.
- Path 속성은 일부러 지정하는 경우가 많은데 특정 경로(폴더명)를 설정한다.
- Path 속성은 /폴더/ 로 설정되는데, 이렇게 설정한 Path 속성이 설정되면 현재 폴더 또는 현재 폴더의 하위에서 접근할 수 있다.
- Path 속성을 ' / '로 설정하면 도메인 내의 모든 곳에서 접근할 수 있다. (쿠키의 범위를 좁게 잡을 수록 보안에는 좋다.)
⑥ HttpOnly 속성
- 설정 시 자바스크립트에서 쿠키에 접근할 수 없으므로 쿠키 조작을 방지하기 위해 설정하는 것이 좋다.
쿠키를 생성하고, 웹페이지 새로고침을 하면,
이제 브라우저가 쿠키를 가지고 서버에게 요청(Request) 하게 된다.
그래서 서버쪽에서는 요청한 클라이언트가 누구인지 알수 있게된다.
이러한 로직이 로그인 로직이라고 보면 된다.
Passport 모듈
Passport는 이름 그대로 서비스를 사용할 수 있게끔 해주는 여권 같은 역할을 하는 모듈이다.
회원가입과 로그인은 직접 구현할 수도 있지만, 세션과 쿠키 처리 등 복잡한 작업이 많으므로 검증된 모듈을 사용하는 것이 좋다.
그런 방면에서, Passport는 사용하기 좋은 검증된 모듈이다.
클라이언트가 서버에 요청할 자격이 있는지 인증할 때에 passport 미들웨어를 사용한다고 볼 수 있다.
서비스에 로그인할 때 아이디와 비밀번호 이외에 구글, 페이스북, 카카오 같은 기존의 SNS 서비스 계정을 이용하여 로그인 하는데, passport모듈도 이런 OAuth 로그인도 가능하다.
자세한 인증 방법은 아래 참고 사이트 그대로 사용했으니 참고하면 된다!!
// session-cookie 설정 부분
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
resave: false, // 세션 항상 저장할지
saveUninitialized: true, // 세션 저장 전 Uninitialized 상태로 만들어 저장
secret: process.env.COOKIE_SECRET, // 암호화 키
store: sessionStore, // Sequelize로 설정한 MySQL 저장소를 사용
cookie: {
domain: 'https://www.smile-mbti.shop', // 문제였던 부분
httpOnly: true,
secure: true,
sameSite: "none",
maxAge: 1000 * 60 * 60 * 24 * 7,
},
})
);
// CORS 설정 부분
app.use(
cors({
// front 서버인 127.0.0.1:8080 의 요청을 허용하도록 cors 사용
origin: [process.env.FRONT_URL_1, process.env.FRONT_URL_2],
methods: ["GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS"],
optionsSuccessStatus: 200,
credentials: true,
})
);
이게 문제의 그 코드이다.
다른 도메인 간의 쿠키 공유에 대한 많은 자료를 찾아봤다면 분명 다들 같은 설정을 했을것이다.
httpOnly : true를 하면 secure : true도 설정해야 하고 같은 사이트가 아니기 때문에 sameSite : none도 설정해야된다 등등
물론 그렇게 했지만 계속 쿠키가 생기기만 하고 세션에 저장이 되지 않는 문제가 생겼었다.
여러가지를 찾아보던 중 nginx를 활용하면 된다는 글을 봤다. 하지만 스스로 nginx에 대한 정보가 부족하다고 판단하여 다른 해결 방법을 찾아보았다.
다른 방법으로는 도메인을 사서 같은 도메인으로 연결하는 방법이었다.
가비아에서 도메인을 사고 front와 back의 url을 달리하여 연결했다.
(가비아는 설정 적용이 느리기 때문에 Cloudflare에 도메인을 연결하여 설정을 빠르게 진행하는 것을 추천한다!)
cloudtype에서 받은 각각의 CNS 설정 CNAME 값과 TXT 값을 연결해서 시도해봤지만 axios 404 에러를 띄우며 쿠키가 전달이 되지 않았다.
그러다 문득 Network에 있는 console을 확인하다가 set-cookie에서 이 문장을 읽게 되었다.
`the attempt to set a cookie via set-cookie header was blocked because its domain attribute was invalid with regards to the current host url`
'url 설정이 제대로 되어있지 않아 쿠키 전달이 금지되었다' 는 내용이다.
구글에 그대로 검색을 하고 바로 해결을 했다.
해당 글의 맨 밑의 답변에 있는 domain 설정을 보면 'smile-mbti.shop' 이렇게 지정해야 같은 도메인으로 인식을 하는데 같은 도메인으로 굳이 만들어놓고는 계속 프론트 도메인인 'https://www.smile-mbti.shop만 도메인 허용을 해준게 문제였다.
해결코드
// session-cookie 설정 부분
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
resave: false, // 세션 항상 저장할지
saveUninitialized: true, // 세션 저장 전 Uninitialized 상태로 만들어 저장
secret: process.env.COOKIE_SECRET, // 암호화 키
store: sessionStore, // Sequelize로 설정한 MySQL 저장소를 사용
cookie: {
domain: 'smile-mbti.shop', // 문제였던 부분
httpOnly: true,
secure: true,
sameSite: "none",
maxAge: 1000 * 60 * 60 * 24 * 7,
},
})
);
// CORS 설정 부분
app.use(
cors({
// front 서버인 127.0.0.1:8080 의 요청을 허용하도록 cors 사용
origin: [process.env.FRONT_URL_1, process.env.FRONT_URL_2],
methods: ["GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS"],
optionsSuccessStatus: 200,
credentials: true,
})
);
설정까지 다 해놓고 이상한 데서 삽질을 하다가 몇 분 만에 해결하니 처음에는 너무 오래 걸린 걸 해결해서 뿌듯했지만 좀 허무하기도 했다. console이나 에러메세지를 잘 확인해야겠다고 또 다짐하게 되었다.
참고
-passport 모듈 활용 사이트
- 다른 도메인간 쿠키 전송