프로젝트를 처음해 본다면 꼭 보게되는 에러 중 하나는 바로 Cors에러이다.
기획이 얼추 정리가 되면 백엔드 개발자와 프론트엔드 개발자는 큰 문제 없이 서로의 개발을 마무리하게 된다.
그러나 API를 연결하기 시작할 때부터 기습적으로 발생하는 Cors에러는 재앙과도 같다.
이 악명높은 Cors에러를 해결해보자.
Cors란 Cross-Origin Resource Sharing
의 약자로 한국어로는 교차 출처 리소스 허용
이라고 한다.
이 까다로운 용어를 이해하기 위해서는 출처(Origin)
가 무슨 말인지 알아야한다.출처(Origin)
는 Http://11.22.33.44:80
처럼 protocol
, ip
, port
로 이루어지며 프론트 페이지를 받은 곳과 백엔드 API를 요청하는 곳 모두 출처(Origin)
라고 한다.
즉, 리액트 개발자라면 http://localhost:3000
이 출처(Origin)
라는 뜻이다.
이 출처(Origin)
에서 또 다른 출처(Origin)
로 요청을 보내게 된다면 이때 리소스를 공유해 준다는 것이 Cors
이다.
그러나 우리가 따로 작업을 해주지 않는다면 당연하게도 출처(Origin)
가 다르기 때문에 브라우저는 보안을 이유로 에러를 띄우게 되는데 이것이 바로 악명 높은 Cors
에러 이다.
예시를 들어보자면,
http://localhost:3000
로 보는 페이지에서 우리 백엔드 개발자가 준출처(Origin)
인http://11.22.33.44:80
으로 요청을 보낸다면 높은 확률Cors
에러가 발생한다.
크게 두 가지 선택지가 있다.
당연하게도 Cors
에러는 출처(Origin)
가 다르기 때문에 생기는 에러이다. 출처를 동일하게 해준다면 Cors
에러는 발생하지 않는다.
app.use(express.static(REACT_BUILD_DIRECTORY));
app.use((req, res, next) => {
res.sendFile(path.join(REACT_BUILD_DIRECTORY, 'index.html'));
});
위 코드 처럼 백엔드 API 서버에서 리액트 빌드 페이지를 서브하면 된다.
이 경우, http://11.22.33.44:80
로 요청하여 페이지를 받게 되며 http://11.22.33.44:80/post/all
처럼 API를 요청하게 된다.
이 경우는 Same-Origin Policy
라고 한다. 한국어로는 동일 출처 정책
이라고 한다.
그러나 이 경우 리액트 페이지 경로와 백엔드 API 경로가 일치하여 문제가 발생할 수 있다. 2번 방법을 적극적으로 사용하도록 하자.
브라우저는 http://localhost:3000
라는 프론트 출처(Origin)
에서 http://11.22.33.44:80
라는 백엔드 출처(Origin)
로 요청하게 된다면 교차 출처에 대해 리소스 공유를 허용할 지 판단하게 된다. 이 때 브라우저는 Access-Control-Allow-Origin
라는 헤더를 확인한다.
즉, 브라우저에서 백엔드 출처(Origin)
로 요청을 보내고 응답 헤더에 프론트 출처(Origin)
가 담겨 있는지 확인한다.
응답 헤더에 프론트 출처(Origin)
를 담아주는 과정을 전역 미들웨어로써 등록할 수 있는데, 더욱 간단하게 npm에서 cors 라이브러리를 사용할 수 있다.
app.use(cors({
origin: 'http://localhost:3000',
}));
추가적으로,Cors
와 관련된 헤더는 더 자세하게 건드릴 수 있으므로 npm 사용법 또는 관련 헤더를 더 찾아보는 것을 추천한다.
로그인 시, 쿠키가 발급되지 않거나 쿠키를 강제로 삽입한 후 요청을 보내더라도 쿠키가 서버에서 확인되지 않은 경우가 있다.
이 경우 프론트엔드 개발자와 백엔드 개발자 모두 힘써줘야한다.
fetch
를 사용하는 경우, credentials
를 include
로 보내도록 한다.
fetch(url, {
credentials: 'include'
});
Access-Control-Allow-Credentials
헤더를 true
로 설정해준다.
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
}));
그러나 이 경우 크롬에서는 Access-Control-Allow-Origin
헤더가 *
이면 안된다.
브라우저에서 에러가 발생한다. 서버에서는 아무런 에러가 발생하지 않는다. 주의하도록 하자.
app.use(cors({
origin: '*', // 브라우저에서 에러 발생
credentials: true,
}));
이 경우 브라우저의 보안이 강화되어 https
가 아닌 http
프로토콜에는 브라우저가 쿠키를 발급해주지도, 보내주지도 않는다.
크롬 최신 버전의 경우 SSL 인증서가 적절하지 않으면 문제가 발생한다. 다음의 해결 방법을 사용할 수 있다.
ngrok
같은 프록시 서버를 경유하도록 할 수 있다. https
프로토콜을 제공하기 때문에 브라우저 보안 정책을 우회할 수 있다.
도메인을 저렴한 가격에 일단 구매한 후 Let's Encrypt
와 같은 무료 인증서를 활용하여 백엔드 서버에 도메인 + 인증서를 붙인다.
그러나 인증서 발급을 위한 도메인이 필요하며 비용이 발생한다.
요청 헤더 중 Authorization
이라는 헤더가 존재한다. 인증 관련 토큰을 담기 위해 만들어진 헤더로 쿠키를 통해 전달하는 것이 아닌 직접 담아 전달하는 방법이다.
이 경우 문제를 회피하는 것이며 많은 코드를 수정해야할 수도 있다. 그러나 개발 단계에서 쿠키에 대한 큰 불편함을 가지고 있다면 고려해볼 수 있다.
Cors
에러는 백엔드와 프론트엔드와 서로 잘 협력하여 해결해야 한다.
P.S. 첫 프로젝트에는 정말 다양한 장애물이 있습니다. 그러나 하나 둘 이겨내다보면 어느덧 한층 성장한 자신을 볼 수 있습니다.
고생 많으셨습니다.