CORS preflight 실패로 인한 인증 실패와 403 원인 분석 및 해결 가이드
문제 정의 — CORS preflight 실패가 인증 흐름을 깨뜨리는 이유
증상은 브라우저 콘솔에 "Access to XMLHttpRequest at '…' from origin '…' has been blocked by CORS policy" 또는 "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header" 같은 메시지로 나타납니다. 겉으로는 클라이언트가 403이나 네트워크 오류를 받지만, 실제 원인은 OPTIONS(preflight) 단계에서 필요한 헤더가 누락되거나 차단되어 인증 토큰(Authorization 헤더나 쿠키)이 서버에 전달되지 않기 때문입니다. 브라우저는 preflight가 실패하면 실제 요청을 보내지 않거나 응답을 차단하므로 서버는 인증 정보가 없는 요청으로 처리해 401/403을 반환합니다. 실무 체크리스트: OPTIONS 응답에 필수 CORS 헤더가 포함되어 있는지, Access-Control-Allow-Headers에 Authorization 또는 필요한 커스텀 헤더가 명시되어 있는지, 그리고 withCredentials 사용 시 Access-Control-Allow-Credentials와 Origin 설정이 일치하는지를 먼저 확인하세요. (이 문제는 CORS preflight 실패로 인한 인증 실패와 403 원인과 직접 연결됩니다.)
- 서버가 OPTIONS 엔드포인트도 인증 대상으로 설정해 프리플라이트 단계에서 인증을 요구하는 경우
- Access-Control-Allow-Headers에 Authorization 또는 커스텀 헤더가 누락된 경우
- withCredentials를 사용하면서 Access-Control-Allow-Credentials가 없거나 Origin에 와일드카드('*')를 쓰는 등 자격증명과 충돌하는 경우
- 리버스 프록시나 방화벽이 OPTIONS 요청을 차단하거나, 응답에서 CORS 관련 헤더를 제거하는 경우
CORS preflight(OPTIONS) 요청의 동작 원리와 필수 헤더
브라우저는 교차 출처 요청에 '안전하지 않은' 메서드나 커스텀 헤더가 포함되면 실제 요청 전에 OPTIONS(preflight) 요청을 보내 서버의 허용 여부를 확인합니다. 이 preflight 요청에는 Origin, Access-Control-Request-Method, Access-Control-Request-Headers가 포함되며, 브라우저는 서버 응답을 보고 실제 요청과 인증 정보(쿠키·토큰) 전송 여부를 결정합니다. preflight 응답이 불충분하면 브라우저는 실제 요청을 차단하고 인증 정보가 전송되지 않아 403 또는 인증 실패가 발생할 수 있습니다. 실무 체크리스트: 요청/응답 헤더가 정확히 설정되어 있는지, Access-Control-Allow-Credentials와 Origin 값이 일치하는지, 그리고 Access-Control-Allow-Headers에 필요한 커스텀 헤더가 포함되어 있는지 먼저 확인하세요. 특히 CORS preflight 실패로 인한 인증 실패와 403 원인을 점검할 때 이 항목들을 우선 검토하면 문제를 빠르게 좁힐 수 있습니다.
- 요청(브라우저) 헤더:
- Origin — 요청 출처를 식별
- Access-Control-Request-Method — 실제 요청에서 사용할 HTTP 메서드
- Access-Control-Request-Headers — 요청에서 사용될 커스텀 헤더 목록
- 서버(응답) 필수 헤더와 역할:
- Access-Control-Allow-Origin — 허용된 출처(또는 *)를 명시
- Access-Control-Allow-Methods — 허용된 메서드 목록(요청한 메서드 포함)
- Access-Control-Allow-Headers — 요청한 커스텀 헤더들을 허용하거나 와일드카드 사용
- Access-Control-Allow-Credentials — 인증 정보 허용 여부(true면 Origin에 정확한 값 필요)
- Access-Control-Max-Age — preflight 결과를 캐시하는 시간(초)
인증 실패와 403으로 이어지는 주요 원인
CORS preflight(OPTIONS) 요청이 실패하면 브라우저는 인증 헤더나 쿠키를 실제 요청에 포함하지 않습니다. 그 결과 서버는 인증 정보가 부족하다고 판단해 401이나 403을 반환할 수 있습니다. 실무 체크: preflight 응답에 Access-Control-Allow-Credentials, 정확한 Access-Control-Allow-Origin 값, Authorization 헤더 허용 여부, 허용 메서드와 2xx 응답 상태를 우선 점검하세요. 요약하면, CORS preflight 실패로 인한 인증 실패와 403 원인을 빠르게 확인하는 것이 중요합니다.
- Access-Control-Allow-Credentials 누락 — 서버 응답에 Access-Control-Allow-Credentials: true가 없으면 브라우저는 쿠키나 인증 헤더를 전송하지 않습니다. 해결 방법: 응답에 Access-Control-Allow-Credentials: true를 포함시키세요.
- 허용된 Origin 불일치 — credentials을 허용할 때는 Access-Control-Allow-Origin에 와일드카드(*)를 사용할 수 없습니다. 요청 Origin과 응답의 Origin 값이 정확히 일치하지 않으면 브라우저가 차단해 인증이 실패합니다.
- Authorization 헤더 차단 — preflight 응답의 Access-Control-Allow-Headers에 Authorization이 포함되지 않으면 OPTIONS가 실패합니다. 그 결과 실제 요청에 Authorization 헤더가 실리지 않아 서버가 403을 반환할 수 있습니다.
- 쿠키 미전송 — 클라이언트에서 withCredentials를 사용하지 않거나, 서버의 Set-Cookie가 SameSite 또는 secure 설정으로 인해 교차 출처 전송을 막으면 인증 세션이 전달되지 않습니다.
- 메서드/응답 상태 문제 — preflight에서 허용되지 않은 HTTP 메서드(예: PUT)를 사용하거나 OPTIONS가 2xx가 아닌 상태 코드로 응답하면 브라우저가 요청을 중단해 인증이 실패할 수 있습니다.
리버스 프록시·API 게이트웨이·CDN에서 자주 발생하는 설정 문제
리버스 프록시, API 게이트웨이, CDN 환경에서 특히 CORS preflight 실패로 인한 인증 실패와 403 원인이 되는 경우가 많습니다. 아래는 주요 원인과 점검 포인트입니다.
- OPTIONS 차단: 프록시나 게이트웨이가 OPTIONS 요청을 기본적으로 차단하거나 별도 인증을 요구하면 preflight가 403으로 종료됩니다. 대응 방법은 OPTIONS 요청을 인증·ACL 검사에서 제외하거나 허용 규칙을 추가하는 것입니다.
- 응답 헤더 제거/변형: Access-Control-Allow-Origin/Methods/Headers, Vary 또는 Access-Control-Allow-Credentials 같은 CORS 관련 헤더가 프록시나 캐시에서 삭제되거나 변형되면 브라우저가 요청을 차단합니다. 해결책은 헤더 패스스루를 활성화하거나 응답 편집 기능을 비활성화하는 것입니다.
- 캐시 정책 영향: CDN이 OPTIONS 응답을 캐시하거나 CORS 관련 헤더를 무시하면 이후 요청에서 잘못된 응답이 반환됩니다. 권장 조치는 OPTIONS에 대해 캐시를 비활성화하거나 Cache-Control과 Vary를 올바르게 구성하는 것입니다.
- WAF 규칙: WAF가 의심스러운 헤더나 패턴을 이유로 preflight를 비정상 요청으로 분류해 차단할 수 있습니다. 대응으로는 WAF에서 OPTIONS나 특정 경로를 예외 등록하고 서명·룰을 조정하는 것을 권합니다. 실무 체크리스트 예: ① OPTIONS 허용 설정 확인 ② CORS 헤더 패스스루 점검 ③ CDN 캐시/Cache-Control 설정 검토 ④ WAF 예외 및 룰 튜닝.
실무 진단 체크리스트와 필수 도구
재현 → 캡처 → 분리 순으로 진행합니다. 목표는 브라우저의 preflight(OPTIONS)와 실제 요청이 서버에서 어떻게 처리되는지 확인해 403 원인을 좁히는 것입니다(특히 CORS preflight 실패로 인한 인증 실패와 403 원인 여부를 확인).
- 브라우저 DevTools — Network 탭에서 OPTIONS 요청과 응답 헤더(Access-Control-Allow-*)를 확인하고, Console에서 CORS 관련 에러 메시지를 캡처하세요.
- curl / HTTPie — Origin 및 Access-Control-Request-Method/Headers를 포함한 OPTIONS 요청으로 서버 응답을 직접 검증합니다. 자격증명(Credentials) 허용 여부도 확인합니다.
- tcpdump — 네트워크 레벨에서 패킷 손실이나 리다이렉트가 있는지 검사하고, 프록시나 L7 장비에서 응답이 변경되는지 감시하세요.
- 서비스 로그 — 인증 모듈, 프록시(ingress), API 게이트웨이 로그에서 403이 발생한 시점의 요청과 토큰 정보를 서로 대조합니다.
- 분산 트레이스 — 요청 흐름을 따라가며 preflight가 인증 체인에서 차단되는지, 또는 타임아웃·리트라이로 인한 실패인지 판별합니다.
우회 검증: 프록시나 로드밸런서를 경유하지 않고 직접 호출해 문제의 원인이 서버 내부인지 인프라인지 분리하세요. 실무 체크리스트 예: 브라우저에서 재현 → curl로 직접 OPTIONS 검증 → 관련 로그와 트레이스를 비교.
해결 패턴과 예방 전략 — 설정 예시 및 운영 권장사항
정책은 명확해야 합니다. 인증이 필요한 요청에는 Access-Control-Allow-Origin에 와일드카드(*)를 쓰지 말고, 클라이언트 출처(origin)를 그대로 반영하거나 화이트리스트를 적용하세요. 또한 Access-Control-Allow-Credentials: true를 설정하고, 캐시 오염을 막기 위해 반드시 Vary: Origin 헤더를 추가해야 합니다.
- Preflight 캐싱:
Access-Control-Max-Age를 적절히 설정(예: 600)해 OPTIONS 요청이 지나치게 자주 발생하지 않도록 제한하세요. - 자격증명 정책: 브라우저는
withCredentials=true일 때만 쿠키나 기본 인증을 전송합니다. 서버와 클라이언트 설정이 일치하는지 확인하세요. - 게이트웨이/프록시: Origin 헤더를 보존하고, OPTIONS 요청을 백엔드로 라우팅하거나 프록시 레벨에서 명시적으로 응답을 반환하도록 구성하세요.
- CI·테스트: 자동화된 E2E·통합 테스트에서 OPTIONS 응답, 응답 헤더 및 상태 코드(200/204)를 검증하세요. 실패 시 롤백 조건을 설정하고, 체크리스트(OPTIONS 검사, 크레덴셜 전송 확인,
Vary: Origin포함 여부)를 포함해 테스트하세요. E2E 과정에서 CORS preflight 실패로 인한 인증 실패와 403 원인을 의심할 경우 관련 로그를 함께 확인합니다.
운영 권장: Origin과 OPTIONS 실패를 로그로 수집해 모니터링하고, 이상 탐지 시 알림을 설정하세요. 배포 전 스테이징 환경에서 CORS 회귀 테스트를 반드시 수행하십시오.
경험에서 배운 점
요약하면, 브라우저의 CORS preflight는 실제 인증 흐름과 별개로 먼저 발생하는 OPTIONS 요청입니다. 이 프리플라이트가 403 또는 기타 오류로 처리되면 브라우저는 인증 토큰을 전송하지 않고 실제 요청을 차단합니다. 현장에서 자주 보는 실수는 (1) 인증 미들웨어가 OPTIONS를 차단하거나, (2) 오류 응답(403/500)에 CORS 헤더를 누락하거나, (3) 자격증명(쿠키/Authorization)을 쓸 때 Access-Control-Allow-Origin을 '*'로 둔 경우입니다. 겉으로는 인증 실패처럼 보이지만 원인은 대부분 preflight 처리나 응답 헤더의 불일치에 있습니다. CORS preflight 실패로 인한 인증 실패와 403 원인을 염두에 두고 접근하면 원인 규명이 빨라집니다.
- OPTIONS(preflight)에 대해 인증을 강제하지 않거나, 최소한 CORS 응답 헤더는 항상 포함되도록 서버를 설정한다.
- Access-Control-Allow-Credentials: true를 쓸 때는 응답에 정확한 Origin을 넣고 와일드카드('*')는 사용하지 않는다. 또한 Vary: Origin을 설정하라.
- Access-Control-Allow-Methods와 Access-Control-Allow-Headers에 브라우저가 실제로 보내는 값(특히 커스텀 헤더와 Content-Type)을 포함시킨다.
- CDN, WAF, 로드밸런서 등이 OPTIONS를 차단하거나 CORS 헤더를 제거하지 않는지 점검한다.
- 오류(403/500) 응답에도 동일한 CORS 헤더를 붙여두면 브라우저에서 원인을 파악하기 쉬워진다.
디버깅과 재발 방지 관점에서는 브라우저 개발자 도구의 네트워크 탭에서 OPTIONS 요청과 응답 헤더를 먼저 확인하는 것을 기본으로 삼아야 합니다. curl --request OPTIONS --header "Origin: ..."로 수동 테스트를 해보고, 서버 로그에서 METHOD=OPTIONS와 응답 코드 패턴을 조회하세요. 운영적으로는 프리플라이트 실패(OPTIONS 4xx/5xx)가 증가하면 알람을 걸고, 인프라 코드(ingress/nginx/middleware)로 CORS 설정을 표준화해 배포 파이프라인에서 관리하는 것이 효과적입니다.
- 개발·스테이징 환경에서는 자동화된 preflight 테스트(OPTIONS 포함)를 CI에 포함한다. 체크리스트 예: OPTIONS 반환 코드, 필수 CORS 헤더(Origin/Methods/Headers), 인증 미들웨어 순서 확인.
- 운영 알람: OPTIONS에 대한 4xx/5xx 비율 급증을 감지하도록 설정한다.
- CORS 설정을 문서화하고 인프라 코드로 관리해 배포 시 누락을 방지한다.
- 보안 검토: 자격증명 허용 시 오리진 화이트리스트를 유지하고 '*' 사용은 금지한다.
- 문제가 발생하면 우선 OPTIONS 응답 헤더와 라우팅/미들웨어 실행 순서를 확인해 원인을 좁힌다.
댓글
댓글 쓰기