웹 개발
CORS 에러를 프론트 수정으로만 풀려다 놓친 preflight 요청
브라우저 콘솔의 CORS 에러를 보고 API 코드부터 고쳤다가, OPTIONS 요청과 응답 헤더를 따로 확인하며 해결한 기록.
브라우저에는 한 줄만 보였다
관리자 화면에서 POST /api/invites를 붙였을 때였다. 로컬에서는 되는데 운영 도메인에서만 “No Access-Control-Allow-Origin”이 보였다. 처음에는 React의 fetch 옵션이나 인증 헤더를 의심했다. 하지만 Network 탭을 펼치니 실제 POST는 서버에 도착하지도 않았고, 그 앞의 OPTIONS 요청이 404였다.
CORS는 프론트가 우회해서 해결할 문제가 아니다. 브라우저가 다른 출처 요청 전에 보내는 preflight와 서버 응답을 따로 봐야 했다.
먼저 요청을 둘로 나눠 본다
Authorization 헤더나 JSON POST처럼 단순하지 않은 요청은 OPTIONS가 먼저 온다. DevTools에서 Method 열을 켜고 다음 네 가지를 적었다.
- OPTIONS의 상태 코드와 응답 헤더
- 실제 POST가 전송됐는지
- Origin 값이 운영 주소와 정확히 같은지
- 허용 메서드와 허용 헤더 목록
서버 밖에서도 OPTIONS만 재현할 수 있다.
curl -i -X OPTIONS https://api.example.com/api/invites -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: authorization,content-type'
여기서 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers가 요청과 맞아야 한다. 와일드카드 Origin과 쿠키 인증을 동시에 쓰면 브라우저가 허용하지 않는 점도 헷갈리기 쉬웠다.
Express에서는 OPTIONS도 경로에 닿아야 한다
내 경우 Nginx가 OPTIONS를 앱까지 넘기지 않는 설정이 원인이었다. 앱 쪽 처리는 다음처럼 명시했다.
const allowedOrigin = 'https://app.example.com';
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', allowedOrigin);
res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
운영에서는 허용 Origin을 환경변수에서 읽되, 요청 Origin을 그대로 반사하지 않았다. 개발 편의 때문에 모든 출처를 허용하는 코드는 나중에 잊기 쉽다.
다음에 같은 화면을 보면
CORS 문구만 보고 fetch를 여러 번 바꾸지 않기로 했다. OPTIONS가 성공한 뒤 POST가 서버 로그에 남는지까지 확인하면, 프론트 문제와 프록시 문제를 훨씬 빨리 가를 수 있다.