/개발/Fixing CORS Errors: Diagnosis Table by Console Error Message + Copy-Paste Configs by Server
DevelopmentCORS 에러 해결Access-Control-Allow-Origin

Fixing CORS Errors: Diagnosis Table by Console Error Message + Copy-Paste Configs by Server

Diagnose CORS errors by their verbatim console messages—missing Access-Control-Allow-Origin, preflight failures, credentials + wildcard conflicts—and fix them immediately with copy-paste configs for Express, Nginx, Spring Boot, and FastAPI.

Fixing CORS Errors: Diagnosis Table by Console Error Message + Copy-Paste Configs by Server

The Complete CORS Error Fix: Diagnosis Table by Console Error Message + Copy-Paste Configs by Server

You've almost certainly been there: the API returns 200 just fine in Postman, but the browser console lights up red. And nine times out of ten you think, "The code is clearly correct—why is this blocked?"

The first misconception to clear up: CORS is not the server rejecting the request—it's the browser intercepting the response. The server sent a perfectly normal response, but if that response lacks the right Access-Control-* headers, the browser says "I can't hand this to you for security reasons" and blocks the result from reaching JavaScript. That's why curl and Postman work while the browser blows up.

This post is structured as error message → cause → server config. Take the English console message as-is, look it up in the diagnosis table, then paste the config for your server stack.

CORS in 30 Seconds

Because of the Same-Origin Policy, browsers block responses from a different origin (protocol + host + port) by default. Calling https://api.com from https://app.com is a different origin (cross-origin), so CORS checks kick in.

Requests fall into two categories.

  • Simple request: GET, POST (HEAD), and Content-Type of text/plain, application/x-www-form-urlencoded, or multipart/form-data. The actual request is sent immediately.
  • Preflight: Non-simple methods like PUT/DELETE/PATCH, custom headers like Authorization, or Content-Type: application/json cause the browser to send an OPTIONS request first, asking "Is this method/header allowed?" before the actual request.

Remember this: the moment a REST API sends a JSON body, you almost always get a preflight. That accounts for about half of all CORS errors.

Diagnosis Table by Error Message

Find the console message in the table below.

Console error (verbatim)Cause typeOne-line diagnosis
No 'Access-Control-Allow-Origin' header is present on the requested resource① Missing response headerServer isn't sending CORS headers at all
The value of the 'Access-Control-Allow-Origin' header ... must not be the wildcard '*' when the request's credentials mode is 'include'③ Wildcard + credentials conflictYou cannot use * together with cookie auth
Response to preflight request doesn't pass access control check② Preflight failureOPTIONS response is bad (4xx / missing headers)
Method PUT is not allowed by Access-Control-Allow-Methods④ Allow-Methods missingPUT is not in the allowed methods list
Request header field authorization is not allowed by Access-Control-Allow-Headers④ Allow-Headers missingauthorization is not in the allowed headers list
... has been blocked by CORS policy ... contains multiple values '...'⑤ Duplicate headersProxy and backend both adding the header

Precise Diagnosis by the 5 Cause Types

① Missing response headers on a simple request

This is the most common one. The server response has no Access-Control-Allow-Origin at all. Open Network tab → the request → Response Headers and check whether access-control-allow-origin is present. If not, CORS config is missing on the server.

② Preflight OPTIONS failure

In the Network tab you'll see a gray OPTIONS request right before the actual request. If that returns 404/405 or has no CORS headers, the actual request never fires. Often the router simply doesn't handle OPTIONS.

③ Credentials + wildcard conflict

The frontend set credentials: 'include' (send cookies) but the server responded with Access-Control-Allow-Origin: *. The browser rejects this because "allow every origin + send cookies" is not allowed for security reasons.

④ Missing Allow-Methods / Allow-Headers

If the preflight response's Access-Control-Allow-Methods or Access-Control-Allow-Headers doesn't cover the actual request, it gets blocked. Classic case: you send an authorization header but the server didn't put it on the allow list.

⑤ Header loss or duplication via proxy/redirect

If a reverse proxy like Nginx and the backend both add CORS headers, you get a multiple values error. Conversely, going through a 301/302 redirect can strip CORS headers. Make exactly one layer responsible for CORS headers.

Demo: the wildcard + credentials trap

This combination does not work.

JavaScript
// ❌ 브라우저가 거부: '*' + 쿠키 동시 사용 불가
fetch('https://api.com/me', { credentials: 'include' })
// 서버 응답: Access-Control-Allow-Origin: *
//           Access-Control-Allow-Credentials: true  → 에러

The correct pattern is to reflect Origin dynamically, but only after whitelist validation.

JavaScript
const allowList = new Set(['https://app.com', 'https://admin.app.com']);
function setCors(req, res) {
  const origin = req.headers.origin;
  if (allowList.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin); // '*' 아님!
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin'); // 캐시 오염 방지
  }
}

If you omit Vary: Origin, a CDN/proxy can cache one origin's response and serve it to another. Always include it.

Copy-paste configs by server

Express (cors middleware)

JavaScript
const cors = require('cors');
const allowList = ['https://app.com', 'https://admin.app.com'];

app.use(cors({
  origin: (origin, cb) => {
    // origin이 없는 경우(서버간 호출, 동일 출처)도 허용
    if (!origin || allowList.includes(origin)) return cb(null, true);
    cb(new Error('Not allowed by CORS'));
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));
// cors 미들웨어가 OPTIONS 프리플라이트를 자동 처리합니다.

Nginx (add_header + preflight branch)

Nginx
location /api/ {
  set $cors_origin "";
  if ($http_origin ~* (https://app\.com|https://admin\.app\.com)) {
    set $cors_origin $http_origin;
  }

  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Max-Age 86400 always;
    return 204; # 프리플라이트는 본문 없이 204
  }

  add_header Access-Control-Allow-Origin $cors_origin always;
  add_header Access-Control-Allow-Credentials true always;
  proxy_pass http://backend;
}

⚠️ If the backend (Express, etc.) already adds CORS headers, do not add them in Nginx. Putting them in both causes a multiple values error.

Spring Boot (CorsConfigurationSource)

JAVA
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    // 패턴 사용 시 setAllowedOriginPatterns, 정확한 출처면 setAllowedOrigins
    config.setAllowedOriginPatterns(List.of("https://app.com", "https://admin.app.com"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Content-Type", "Authorization"));
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

If you use Spring Security, you must call http.cors(Customizer.withDefaults()). Otherwise the security filter blocks the request before CORS config can run.

FastAPI (CORSMiddleware)

Python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.com", "https://admin.app.com"],  # '*' 금지
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["Content-Type", "Authorization"],
)

If you combine allow_credentials=True with allow_origins=["*"], Starlette blocks it internally—always use an explicit list.

A note from production

The issue I hit most often running a Next.js frontend against a Spring Boot API was type ⑤—duplicate headers. Locally, backend CORS alone worked fine; once we put ALB + Nginx in front in production, the multiple values error exploded. The takeaway: make a single layer responsible for CORS headers. If you use a BFF or API Gateway, handle CORS only at the gateway and turn it off on the backend—debugging gets much easier. For cookie auth, also set SameSite=None; Secure as a pair, or cookies won't survive cross-domain.

Security checklist & debugging order

  • ✅ Never use Access-Control-Allow-Origin: * + credentials: true in production
  • ✅ Always whitelist-validate Origin, then reflect it
  • ✅ Add Vary: Origin to prevent cache pollution
  • ✅ Add CORS headers in only one of proxy or backend
  • ✅ Watch for CORS header loss on endpoints that go through 301/302 redirects

Debugging order: ① Check the Network tab for an OPTIONS request → ② In the failed response's Response Headers, see which Access-Control-* headers are missing → ③ Map to a cause type in the diagnosis table → ④ Apply the copy-paste config for your stack.

FAQ

Q. It works in Postman but I only get a CORS error in the browser. Why? A. CORS is a browser-only policy. Postman/curl do not apply the Same-Origin Policy, so they get a normal response. The server response headers themselves are fine; what's missing are the CORS headers the browser requires.

Q. Can I really not use the * wildcard? A. For a public API that doesn't send cookies/credentials, * is fine. The moment you use credentials: include (cookie auth), * is forbidden and you must reflect an explicit Origin. A whitelist is also the better security practice.

Q. OPTIONS requests return 404. A. The router isn't handling the OPTIONS method. Express's cors middleware and FastAPI/Spring CORS config handle OPTIONS automatically, but with manual routing you need to add a handler that responds to OPTIONS with 204 + CORS headers.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.