그 CORS 오류는 버그가 아니라 — 실제로 당신의 Web App을 보호하고 있습니다

발행: (2025년 12월 27일 오후 02:37 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

If you’ve worked with APIs in a web app, you’ve probably seen this error at least once:

Access to fetch has been blocked by CORS policy

It feels annoying:

  • Your API URL is correct.
  • Your code looks fine.

But the browser just refuses to cooperate.

At first, it feels like a bug or mis‑configuration. The truth is:

CORS is not an error — it’s a security feature doing exactly what it’s supposed to do.

In this article, let’s understand CORS in simple words, see why it exists, what problems it solves, and why the browser behaves this way.

1. 왜 CORS가 처음에 존재하는가

To understand CORS, imagine a situation without it.

Diagram of the CORS flow and explanations

Suppose you are logged into:

  • facebook.com
  • 혹은 hdfc.com 같은 은행 웹사이트

브라우저는 쿠키나 토큰을 저장해 로그인 상태를 유지합니다.

이제 다른 탭에서 무작위(아마도 악성) 웹사이트를 엽니다. 그 사이트도 JavaScript를 실행합니다.

제한이 없었다면 그 JavaScript는 다음과 같은 요청을 보낼 수 있었을 것입니다:

fetch("https://hdfc.com/api/balance")

요청이 브라우저에서 발생했기 때문에 은행 쿠키가 자동으로 함께 전송됩니다.
은행 서버는 다음과 같이 생각합니다:

“이 요청은 로그인된 사용자로부터 온 것이다.”

그리고 잔액과 같은 민감한 데이터를 반환할 수 있습니다.

이는 위험합니다.

이를 방지하기 위해 브라우저는 동일 출처 정책 (Same‑Origin Policy, SOP) 을 적용합니다:

  • 기본적으로 웹사이트는 동일 출처의 데이터에만 접근할 수 있습니다.

CORS (Cross‑Origin Resource Sharing) 는 이 규칙을 완화할 수 있는 통제된 방법이며, 서버가 명시적으로 허용할 때만 적용됩니다.

2. “Origin”이 정확히 무엇인가?

origin은 세 부분으로 구성됩니다:

부분예시
스킴http 또는 https
호스트도메인 이름 (example.com)
포트80, 443, 5173

이 중 하나라도 변경되면, origin이 다릅니다.

예시

Origin AOrigin B같은가?
https://yuktisahu.devhttps://api.yuktisahu.dev❌ (호스트가 다름)
https://yuktisahu.devhttp://yuktisahu.dev❌ (스킴이 다름)
http://localhost:5173http://localhost:8000❌ (포트가 다름)
https://example.com/page1https://example.com/page2✅ (같은 origin – 경로는 상관없음)

브라우저는 이 정의를 사용하여 요청이 교차‑origin인지 판단합니다.

3. 프론트엔드에서 CORS를 고칠 수 없는 이유

이것은 초보자에게 가장 혼란스러운 부분 중 하나입니다.

브라우저 콘솔에서 오류를 보고 프론트엔드 코드에서 해결하려고 시도합니다.
하지만 CORS는 프론트엔드 코드로 제어되지 않습니다.

실제로 일어나는 일

  1. 프론트엔드가 다른 오리진으로 요청을 보냅니다.

  2. 브라우저가 자동으로 Origin 헤더를 추가합니다.

  3. 서버가 이 Origin을 확인합니다.

  4. 서버가 해당 오리진을 신뢰한다면, 응답에 헤더를 포함합니다:

    Access-Control-Allow-Origin: https://yuktisahu.dev
  5. 헤더가 없거나 오리진과 일치하지 않으면, 브라우저가 응답을 차단하고 JavaScript에서는 절대 볼 수 없습니다.

따라서 서버가 응답을 보내더라도, 브라우저는 그 응답을 코드에 전달하지 않습니다.

그래서 CORS는 프론트엔드가 아니라 서버에서 해결해야 합니다.

4. 왜 Postman에서는 작동하지만 브라우저에서는 안 될까

또 다른 흔한 혼란:

“API가 Postman에서는 동작하는데, 내 웹 앱에서는 왜 실패하나요?”

그 이유는 CORS가 서버 규칙이 아니라 브라우저 보안 규칙이기 때문입니다.

도구CORS 처리 방식
Postman (or curl)브라우저가 아니므로 CORS 적용이 없고, 다른 사이트의 쿠키도 전송되지 않음.
브라우저여러 사이트의 코드를 동시에 실행하고, 민감한 사이트의 쿠키를 저장하므로 데이터 유출을 방지하기 위해 CORS를 강제함.

CORS를 적용하고 강제하는 것은 브라우저뿐이며, Postman과 curl은 해당 규칙을 적용하지 않습니다.

5. 쿠키가 CORS를 더 엄격하게 만든다

때때로 프런트엔드에서 요청에 쿠키를 함께 보내야 할 때가 있습니다:

fetch(url, {
  credentials: "include"
})

이렇게 하면 규칙이 더 엄격해집니다:

  • 서버는 와일드카드(*)로 응답 할 수 없습니다:

    Access-Control-Allow-Origin: *
  • 서버는 허용된 출처를 명시적으로 지정하고, 쿠키를 허용하도록 설정해야 합니다:

    Access-Control-Allow-Origin: https://yuktisahu.dev
    Access-Control-Allow-Credentials: true

이는 의도된 동작입니다.
쿠키가 포함된 경우, 서버는 모든 출처가 아니라 특정 하나의 출처만을 명확히 신뢰해야 합니다.

6. 프리플라이트 요청이란?

일부 요청은 “단순(simple)”합니다. 예:

  • GET
  • 기본 POST (Content-Type: application/x-www-form-urlencoded, multipart/form-data, 또는 text/plain)

다른 요청은 “복잡(complex)”한 것으로 간주됩니다. 예:

  • PUT, PATCH, DELETE
  • 사용자 정의 헤더가 포함된 요청
  • Content-Type: application/json인 요청

이러한 경우 브라우저는 먼저 추가 요청을 보냅니다:

OPTIONS /api

이를 **프리플라이트 요청(preflight request)**이라고 합니다. 서버에 다음과 같이 묻습니다:

“이러한 헤더와 함께 이 종류의 요청을 보내도 괜찮은가요?”

서버가 적절한 CORS 헤더를 포함해 응답하면 브라우저는 실제 요청을 진행합니다. 그렇지 않으면 브라우저는 요청을 완전히 차단합니다.

최종 생각

CORS는 코드를 중단시켜 좌절감을 주지만, 다음을 보호하기 위해 존재합니다:

  • 로그인한 사용자
  • 민감한 데이터
  • 브라우저 보안 경계

이를 이해하면:

  • CORS 오류가 더 의미 있게 다가옵니다.
  • 디버깅이 쉬워집니다.
  • 해결 방법은 항상 서버 측에 있다는 것을 알게 됩니다.

따라서 다음에 CORS 오류가 발생했을 때, “JavaScript에서 고치려” 하기보다는 서버의 CORS 설정을 확인하거나(개발 중이라면 프록시를 사용) 하면 곧 정상으로 돌아올 수 있습니다.

“뭔가 고장났어” 라고 생각하기보다:

“브라우저가 사용자를 보호하고 있으며, 서버가 아직 권한을 부여하지 않은 것입니다.”

Back to Blog

관련 글

더 보기 »

Academic Suite 인증 및 인가

3.1 Academic Suite의 인증 접근 방식 Academic Suite는 JSON Web Token JWT를 사용한 stateless authentication 방식을 사용합니다. session‑based authentication과 달리…