가이드

CORS 오류 해결하는 법 (왜 발생하는지 포함)

CORS 오류는 웹 페이지가 다른 오리진(다른 도메인, 포트, 프로토콜)의 응답을 읽으려 했는데, 그 서버가 이를 허용한다는 헤더를 보내지 않았다는 뜻이에요. 브라우저는 사용자를 보호하기 위해 그 응답을 차단해요. 해결책은 서버에서 여러분 사이트의 오리진에 대해 Access-Control-Allow-Origin 헤더를 반환하고 OPTIONS 프리플라이트 요청을 처리하는 것이고, 또는 자신의 백엔드를 통해 같은 오리진으로 요청을 보내 아예 피하는 것이에요.

요청이 서버에는 보통 잘 도달하기 때문에, 웹 개발에서 가장 헷갈리는 오류 중 하나예요. 이 가이드는 무슨 일이 일어나는지, 그리고 각 변형을 어떻게 고치는지 설명해요.

‘오리진’의 의미

오리진은 프로토콜, 도메인, 포트의 조합이에요.

URL오리진
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com (서브도메인이 달라서 다른 오리진)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000 (포트가 달라서 다른 오리진)

브라우저는 동일 출처 정책을 따라요. 페이지의 JavaScript는 자신과 같은 오리진의 응답은 자유롭게 읽을 수 있지만, 다른 오리진의 응답은 읽을 수 없어요. CORS(Cross-Origin Resource Sharing)는 서버가 옵트인하는 시스템이에요. “내 응답은 이 오리진들의 페이지가 읽어도 된다”고 선언하는 거죠.

사람들을 놀라게 하는 두 가지 결과가 있어요.

  • 요청은 보통 서버에 도달해요. CORS는 대개 여러분의 코드가 응답을 읽는 것을 막을 뿐, 서버가 요청을 받는 것을 막지는 않아요. POST가 저장되었는데도 여러분은 오류를 볼 수 있어요.
  • 브라우저만 이를 강제해요. curl, Postman, 서버 측 코드는 CORS를 완전히 무시해요. 그래서 “Postman에서는 된다”는 CORS에 대해 아무것도 알려 주지 않아요.

1단계: 정확한 오류 읽기

개발자 도구(우클릭 → 검사)를 열고 콘솔에서 전체 메시지를 읽으세요. Chrome의 문구가 어떤 문제인지 알려 줘요.

메시지에 포함된 내용무엇이 잘못되었나
No 'Access-Control-Allow-Origin' header is present서버가 그 헤더를 아예 보내지 않았거나, 요청이 CORS 코드에 도달하기 전에 실패함(예: 404나 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin서버가 여러분과 다른 오리진을 허용함
Response to preflight request doesn't pass access control checkOPTIONS 프리플라이트가 실패함
...must not be the wildcard '*' when the request's credentials mode is 'include'쿠키를 보내고 있는데 서버가 *로 응답함
Request header field authorization is not allowed서버가 Access-Control-Allow-Headers에 그 헤더를 나열하지 않음

그다음 네트워크 탭에서 실패한 요청(그리고 그 앞의 OPTIONS 요청)을 찾아, 상태 코드와 응답 헤더를 보세요.

상태 코드를 먼저 확인하세요. 많은 “CORS 오류”는 사실 변장한 서버 오류예요. 서버가 크래시하거나 404를 반환하면, 그 오류 응답에는 종종 CORS 헤더가 없어서 브라우저가 실제 문제 대신 CORS라고 보고해요. 500이나 404를 고치면 CORS 오류가 사라질 수도 있어요.

2단계: 그 API를 브라우저에서 호출해야 하는지 판단하기

헤더를 추가하기 전에 물어보세요. 브라우저가 이 API를 직접 호출해야 하나요?

  • 비밀 키가 필요한 서드파티 API(OpenAI, Stripe 비밀 키, 대부분의 이메일 서비스)는 절대 브라우저에서 호출하면 안 돼요. 키가 모든 방문자에게 보이니까요. 대신 백엔드에서 호출하세요. 그러면 CORS 문제가 사라져요. 서버 간 요청은 CORS의 적용을 받지 않으니까요. API 키를 안전하게 보관하는 법을 참고하세요.
  • 여러분이 통제하지 않고, CORS 헤더를 보내지 않는 서드파티 API는 여러분 쪽에서 고칠 수 없어요. 백엔드에서 호출하세요.
  • 자신의 API라면 올바르게 설정해서 고칠 수 있어요. 아래에서 계속하세요.

3단계: 서버에서 고치기

서버는 오류 응답을 포함해 모든 응답에 올바른 헤더를 추가해야 해요.

실제 요청에는 다음을 보내세요.

  • Access-Control-Allow-Origin: https://myapp.com (프런트엔드의 정확한 오리진, 끝에 슬래시 없이)
  • 값이 요청한 오리진에 따라 달라진다면 Vary: Origin

프리플라이트 OPTIONS 요청에는 성공 상태(200 또는 204)와 함께 다음을 응답하세요.

  • Access-Control-Allow-Origin: https://myapp.com
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-Control-Allow-Headers: Content-Type, Authorization (보내는 커스텀 헤더 전부)
  • 선택 사항으로 Access-Control-Max-Age: 600을 두면 브라우저가 프리플라이트를 일정 시간 캐시해요.

대부분의 프레임워크에는 이를 처리하는 패키지나 설정이 있어요. Express에서는 cors 패키지, FastAPI에서는 CORSMiddleware, ASP.NET Core에서는 AddCors, Spring에서는 @CrossOrigin이나 CORS 설정이에요. 일반적인 Express 설정 예시는 다음과 같아요.

import cors from "cors";

app.use(cors({
  origin: ["https://myapp.com", "http://localhost:5173"],
  credentials: true,
}));

CORS 처리는 라우트와 인증보다 앞에 두세요. 그래야 로그인 정보가 없는 프리플라이트 요청이 401로 거부되지 않아요.

AI 빌더를 쓴다면 정확하게 설명하세요.

https://myapp.com의 프런트엔드가 https://api.myapp.com의 API를 호출하는데 “Response to preflight request doesn't pass access control check” 오류가 나. API에 CORS를 설정해서 정확히 https://myapp.com과 미리보기 오리진만 허용하고, Content-Type과 Authorization 헤더와 함께 GET, POST, PATCH, DELETE를 허용해 줘. OPTIONS는 인증보다 먼저 처리하고, 오류 응답에도 CORS 헤더를 포함해 줘.

4단계: 쿠키와 인증 정보 처리하기

프런트엔드가 다른 오리진으로 쿠키(예: 로그인 세션)를 보낸다면, 양쪽이 합의해야 해요.

  • 프런트엔드는 fetch에서 credentials: "include"를 설정하거나, axios에서 withCredentials: true를 설정해요.
  • 서버는 Access-Control-Allow-Credentials: true를 보내요.
  • 서버의 Access-Control-Allow-Origin은 *가 아니라 정확한 오리진이어야 해요.

교차 사이트 쿠키에는 쿠키에 SameSite=None; Secure도 필요하고, 일부 브라우저는 서드파티 쿠키를 더 제한해요. 이것이 5단계의 접근법을 선호하는 또 다른 이유예요.

5단계: 동일 출처 설정으로 CORS 피하기

종종 가장 깔끔한 해결책은 아예 교차 출처 요청을 하지 않는 거예요. API를 페이지와 같은 오리진에서 제공하세요. 예를 들어 https://myapp.com/api/...처럼요.

  • 개발 중에는 대부분의 개발 서버가 프록시를 할 수 있어요. Vite의 server.proxy 설정으로 /api를 다른 포트의 백엔드로 전달하면, 브라우저는 한 오리진하고만 통신해요.
  • 운영 중에는 호스트의 리라이트나 프록시 설정으로 /api를 백엔드에 라우팅하거나, 프런트엔드와 API를 한 서버에서 제공하세요.

그러면 프런트엔드는 fetch("/api/orders")처럼 상대 경로로 호출해요. 교차 출처 요청도, 교차 출처 프리플라이트도, 쿠키 문제도 없어요. 또한 미리보기와 운영 환경 사이에서 달라지는 API 도메인을 하드코딩할 필요도 없어요.

프런트엔드가 백엔드를 전체 URL 대신 상대 경로 /api로 호출하도록 바꿔 줘. 그리고 /api가 API 서버로 전달되도록 개발 프록시를 설정해 줘. 더 이상 필요 없는 CORS 설정은 모두 제거해 줘.

하지 말아야 할 것

  • fetch의 mode: "no-cors". 아무것도 우회하지 않아요. 여러분의 코드에 비어 있고 읽을 수 없는 응답만 줄 뿐이에요.
  • CORS를 끄는 브라우저 확장 프로그램. 여러분의 브라우저만 바꿀 뿐이에요. 모든 방문자는 여전히 오류를 봐요.
  • 실제 앱에서 쓰는 공개 “CORS 프록시” 서비스. 그곳을 지나가는 모든 것을 읽고 바꿀 수 있고, 그 서비스는 언제든 중단될 수 있어요.
  • 로그인을 쓰는 API에 Access-Control-Allow-Origin: *. 어차피 인증 정보와 함께 동작하지 않고, 비공개 데이터에 잘못된 기본값이에요.
  • 인증 정보를 허용한 상태에서 어떤 Origin 헤더든 그대로 되돌려주기. 그러면 어떤 웹사이트든 사용자를 대신해 로그인된 요청을 보낼 수 있어요. 허용 목록을 사용하세요.

AI로 만든 앱에서 흔한 원인

상황해결책
프런트엔드가 비밀 키를 가진 서드파티 API를 호출함호출을 백엔드로 옮김
미리보기에서는 되는데 게시된 도메인에서 실패함게시된 오리진과 커스텀 도메인 오리진을 허용 목록에 추가하거나, 상대 경로 /api를 사용
POST, PUT, DELETE에서만 오류프리플라이트가 처리되지 않음. OPTIONS 처리와 허용 헤더를 추가
로그인을 추가한 뒤 오류가 생김인증 정보에는 정확한 오리진과 Allow-Credentials가 필요함
“CORS 오류”인데 네트워크 탭에는 500이 보임서버 오류를 먼저 고침
API URL이 localhost로 하드코딩됨상대 경로나 환경별 설정을 사용

Mythex의 CORS

Mythex 프로젝트에 웹사이트와 API 서버가 있으면, 둘 다 같은 프로젝트의 서비스로 실행되고 미리보기에서 전환해서 볼 수 있어요. 프런트엔드가 개발 중에는 프록시되는 상대 경로 /api를 통해 백엔드를 호출하도록 에이전트에게 요청하면, CORS 문제 대부분을 피할 수 있어요. 교차 출처 호출이 꼭 필요하다면, 콘솔의 정확한 오류와 관련된 오리진을 에이전트에게 알려 주세요. 게시된 앱은 미리보기와 주소가 다르므로 게시된 앱에서도 결과를 확인하세요. 자세한 내용은 문서의 다중 서비스 앱과 API 백엔드, 그리고 AI로 만든 앱 디버깅하는 법과 프런트엔드와 백엔드의 차이 가이드를 참고하세요.

CORS 체크리스트

  • 전체 콘솔 오류와 실패한 요청의 상태 코드를 읽음
  • 숨겨진 404나 500이 아님을 확인함
  • 비밀 키를 쓰는 API는 브라우저가 아니라 백엔드에서 호출함
  • 서버가 정확한 허용 오리진으로 Access-Control-Allow-Origin을 보냄
  • OPTIONS 프리플라이트가 허용된 메서드와 헤더로 응답하며, 인증보다 먼저 처리됨
  • 오류 응답에도 CORS 헤더가 포함됨
  • 인증 정보: 정확한 오리진, Allow-Credentials: true, 올바른 쿠키 설정
  • 미리보기, 게시된 도메인, 커스텀 도메인 오리진을 모두 다루거나 상대 경로 /api를 사용함
  • no-cors, 공개 프록시, 아무 오리진이나 되돌려주는 방식을 쓰지 않음

자주 묻는 질문

CORS 오류는 무슨 뜻인가요?

한 오리진에서 로드된 페이지가 다른 오리진의 응답을 읽으려 했는데, 그 서버의 응답에 이를 허용하는 헤더가 없었다는 뜻이에요. 브라우저는 여러분의 코드가 응답을 읽지 못하도록 막아요. 해결책은 프런트엔드 코드가 아니라 서버에 있거나, 요청이 라우팅되는 방식에 있어요.

프런트엔드에서 CORS 오류를 고칠 수 있나요?

제대로는 고칠 수 없어요. mode: 'no-cors' 같은 설정은 응답을 코드에서 읽을 수 없게 숨길 뿐이에요. 서버가 올바른 Access-Control-Allow-Origin 헤더를 보내도록 하거나, 자신의 백엔드나 동일 출처 프록시를 통해 API를 호출하는 방법으로 고쳐야 해요.

Access-Control-Allow-Origin: * 는 안전한가요?

누구나 읽을 수 있는 공개 데이터, 예를 들어 로그인이 필요 없는 공개 API라면 괜찮아요. 하지만 쿠키나 다른 인증 정보와 함께 쓸 수 없고, 비공개 API라면 허용할 정확한 오리진 목록을 명시해야 해요.

Postman이나 curl에서는 되는데 브라우저에서는 왜 안 되나요?

CORS는 브라우저에서만 적용돼요. Postman이나 curl은 이를 적용하지 않으므로, 그곳에서는 요청이 성공해도 페이지 JavaScript가 같은 요청을 보내면 차단될 수 있어요.

프리플라이트 요청이란 무엇인가요?

JSON POST나 Authorization 헤더가 있는 요청처럼 단순하지 않은 요청에서는, 브라우저가 먼저 OPTIONS 요청을 보내 서버에 어떤 메서드와 헤더가 허용되는지 물어봐요. 그 OPTIONS 요청이 실패하거나 올바른 헤더가 없으면 실제 요청은 아예 전송되지 않아요.

Mythex · Pricing · AI로 무엇이든 만들기 · 웹사이트 및 앱 템플릿 · Mythex 비교 · AI 앱 개발 용어집 · 무료 AI 앱 개발 도구 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)