프로젝트의 클라이언트 페이지, 서버, 데이터베이스를 배포했다. 서버와 데이터베이스는 무료이기도 하고 쉽게 배포를 할 수 있는 cloudtype으로 진행했고, cloudtype의 프리티어로 배포를 진행할 경우 두개정도 배포를 하면 메모리가 꽉 차서 더 이상 배포를 할 수 없기 때문에 클라이언트 페이지는 AWS의 S3로 배포를 했다.
배포 자체는 공식 사이트나 유튜브, 구글링 등으로 자세하게 설명되어있기 때문에 어렵지 않았으나, 배포 이후 발생한 에러때문에 약간 시간이 걸렸다.
배포할때 가장 흔하게 만나는 에러가 아닐까 싶다. 이번 프로젝트에서도 CORS 에러를 만났는데, 우선 cors 모듈을 설치해준 뒤 미들웨어 적용을 해줬다.
cors 모듈 설치하기
npm install cors
미들웨어 적용시켜주기
// server.js 파일
const cors = require("cors");
// ... 다른 코드들 생략
app.use(
cors({
origin: (CORS 요청을 허용해줄 주소 - 로컬호스트, 프론트 배포주소 등등),
credentials: true,
}),
);
클라이언트에서 서버로 토큰 인증을 위해 쿠키를 보내줄 것이기 때문에 credentials 옵션도 true로 설정해주었다.
하지만 이렇게 설정을 해주었음에도 불구하고 계속해서 CORS 에러가 발생했다. 반나절정도 헤매다 한 블로그에서 axios를 사용할 경우 모든 요청에 { withCredentials:true } 설정을 주어야 한다고 해서 따라해봤더니 문제가 해결되었다.
export async function login(eventData: LoginFormData) {
try {
const response = await instance.post(`/login`, eventData, { withCredentials: true });
return response.data;
} catch (error) {
throw error;
}
withCredentials 옵션은 쿠키를 보낼때만 필요한줄 알았어서 쿠키를 첨부해서 보내는 요청에만 적어줬었는데, 헤더에 authorization 항목이 있을때도 적어주어야 한다고 한다. 이번 프로젝트는 시작페이지를 제외한 모든 페이지가 로그인을 해야만 접근할 수 있어서 거의 모든 요청 헤더에 authorization 항목이 들어가기 때문에 에러가 발생했던것 같다.
첫번째 항목에서 적어준 대로 코드를 수정한 후 글 리스트를 받아오거나 글을 쓰고 삭제하는데는 문제가 없었지만, 글을 수정하려고 하니 CORS 에러가 발생했다. 띠용?
이 문제는 Access-Control-Allow-Methods 옵션에 patch 메서드가 포함되지 않아서 생길 수 있다고 한다. Access-Control-Allow-Methods 옵션은 리소스에 접근할 수 있는 HTTP메서드 목록을 의미한다. 그래서 cors 미들웨어에 리소스 접근을 허용할 메서드들을 명시해주었다.
app.use(
cors({
origin: [process.env.CORS, process.env.CORS_DEV],
credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
}),
);
처음에 options 메서드를 안써줬더니 pre-flight 요청에서도 CORS 에러가 발생하길래 options 메서드까지 추가해줬더니 문제가 해결되었다.
로컬 환경에서는 문제없이 쿠키 저장이 되었는데, 클라이언트 배포를 완료하고 로그인을 하니 리프레시 토큰 쿠키가 저장이 되지 않았다. 이것은 크롬의 sameSite 정책이 기본적으로 Lax로 설정되어있기 때문인데, 간단하게 말하자면 쿠키를 보내는곳과 받는 곳이 다르면 쿠키가 보내지지않는 설정이다. (Lax의 경우 일부 예외적인 상황 제외)
우리 프로젝트의 경우 프론트와 백엔드를 서로 다른 배포 서비스로 배포를 했기 때문에 도메인이 완전히 달라서 문제가 생긴 것이었다. 해결방법으로는 프론트와 백의 도메인을 맞추는것이 베스트지만, 우리는 도메인을 맞출수 없으니 우선 sameSite 설정을 None으로 바꿔주었다.
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
sameSite: "None",
secure: true,
});
이렇게 하면 쿠키를 보내는곳과 받는곳이 달라도 쿠키를 보낼수 있다. 다만 이렇게 하면 CSRF 공격의 위험이 높아지기 때문에 secure: true 속성도 함께 주어 암호화된 https 요청에만 쿠키를 보내도록 해야한다.
분명 로그아웃을 했는데 계속 쿠키가 살아났다. 일단 쿠키는 httpOnly로 보내게 되면 브라우저에서 접근할 수도 없고 삭제할 수도 없기 때문에 서버로 로그아웃 요청을 보낸 후 쿠키를 삭제하는 응답을 받아야 한다.
app.get('/logout',(req,res)=>{
res.cookie('refreshToken','',{maxAge:0});
res.status(200).json();
});
이렇게 쿠키의 만료기간을 없애서 쿠키를 삭제하려했으나 잘 되지 않았다. 찾아보니 쿠키를 삭제하려면 쿠키를 보냈을때와 같은 설정으로 삭제해주어야한다고 했다.
res.clearCookie("refreshToken", {
httpOnly: true,
sameSite: "None",
secure: true,
});
res.status(200).json();
maxAge를 0으로 만들어주는 방법을 그대로 사용하면서 옵션만 변경해줄까 했지만, 괜히 maxAge 옵션이 들어가면서 쿠키를 보낼때와 옵션이 달라 쿠키 삭제가 안될까봐 그냥 clearCookie로 했다. (maxAge 옵션은 넣어도 상관없다고 한다.)
이렇게 바꿔줬더니 쿠키 삭제가 안되는 문제도 잘 해결이 됐다!