CORS는 추가 HTTP 헤더를 사용하여, 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
즉, Same-Origin Policy의 문제점을 해결하기 위한 정책인 만큼 출처가 다른 도메인에서의 AJAX요청이라도 서버 단에서 데이터 접근 권한을 허용하는 정책이다.
서버는 평소처럼 요청이 오면 응답을 해줄 뿐이고, 브라우저가 자신이 보낸 요청 및 서버로부터 받은 응답의 데이터가 CORS 정책을 지키는지 검사하여 안전한 요청을 보낸 건지 검사를 진행하게 된다.
따라서 서버가 정상적으로 응답을 해줬더라도, 알고 보니 안전한 요청이 아니라고 판단되면 해당 응답을 버린다. 그렇기 때문에 브라우저를 사용하지 않는 서버 간 통신에서는 이 정책이 적용되지 않는다.
CORS 요청상황

만약 Origin을 포함해 아래와 같이 요청을 보냈다면,
GET / HTTP/1.1
Host: cors.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,en-US;q=0.5
Origin: http://www.acceptmeplease.com
Connection: keep-alive
서버에서 오는 응답은 Access-Control-Allow-Origin 값을 포함하여 올 것이다.
HTTP/1.1 200 OK
Date: Sun, 24 Apr 2016 12:43:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com # << here!!
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: application/xml
Content-Length: 423
<?xml version="1.0" encoding="UTF-8"?>

일반적으로 신뢰할 수 있는 범위를 벗어나는 비정형 특성으로 인해 서버에 대해 추가적인 검증을 진행하는 것이다.
CORS 요청을 보내기 전에 브라우저는 OPTIONS 메서드를 이용하여 확인 요청을 보내는데 이것을 프리플라이트 요청이라고 한다.
OPTIONS 메서드로 Access-Control-Request-Method 및 Access-Control-Request-Headers라는 두 개의 추가 헤더가 정의되어 있는데 이는 추가 HTTP 헤더가 정의된 실제 요청에서 브라우저의 의도를 알려주기 때문에 중요하다.
OPTIONS /resources/post-here/ HTTP/1.1
Host: cors.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Origin: http://www.acceptmeplase.com
Access-Control-Request-Method: POST # << here!!
Access-Control-Request-Headers: X-TOKEN-ID # << here!!
OPTIONS 요청을 수락하면 다음과 같이 응답한다.
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com # << here!!
Access-Control-Allow-Methods: POST, GET, OPTIONS # << here!!
Access-Control-Allow-Headers: X-TOKEN-ID # << here!!
Access-Control-Max-Age: 86400 # << here!!
Vary: Accept-Encoding, Origin
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
응답 코드가 200으로 반환을 해야 프리플라이트 요청이 허용된 것으로 인식한다.

쿠키 등의 인증 정보를 보내기 위해서는 클라이언트 단에서 요청 시 별도의 설정이 필요
Axios를 사용한다면 withCredentials 옵션을, fetch API를 사용한다면 credentials 옵션을 설정해줘야 한다.
이런 설정을 하지 않는다면 쿠키나 Authorization 헤더에 설정하는 토큰 값 등은 절대로 서버에게 전송되지 않는다.
위와 같은 설정을 통해 인증 정보를 요청에 포함시켰다면, 이 요청은 이제 인증 정보를 포함한 요청이 된다.
서버는 이러한 요청에 대해 일반적인 CORS 요청과는 다르게 대응해줘야 한다.
응답의 Access-Control-Allow-Origin 헤더가 와일드카드(*)가 아닌 분명한 Origin으로 설정되어야 하고, Access-Control-Allow-Credentials 헤더는 true로 설정되어야 한다.

CORS 에러가 발생하는 원인은 클라이언트와 서버의 도메인이 달랐을 때 보안 상의 이유로 응답을 받지 못하도록 막은 것이므로 서버 단에서 특정 도메인 혹은 모든 도메인을 허용하도록 설정만 해주면 된다.
Access-Control-Allow-Origin 응답 헤더는 이 응답이 주어진 origin으로부터의 요청 코드와 공유될 수 있는지를 나타낸다. (서버측 응답에서 접근 권한을 주는 헤더를 추가해 cors 에러를 해결하는 방법.)
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인
res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인
});
이외에도 아래와 같은 헤더를 추가할 수 있다.
Access-Control-Allow-Method
Access-Control-Max-Age
Access-Control-Allow-Headers
허나 이는 접근 권한을 허용하는 모든 요청의 응답에 추가해주어야 하므로 만약 모든 요청에 권한 허용을 한다면 모든 응답 전에 위의 헤더를 추가해야 한다는 번거로움이 있다.
사용방법
npm i cors --save
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
위와 같이 아무런 옵션없이 app.use(cors());로 설정한다면 모든 cross-origin 요청에 대해 응답을 해주게 되는데, 만약 모든 요청에 응답하는 것이 아닌 보안상 특정 도메인 요청만 받아야 하는 경우 혹은 특정 요청에만 응답하는 경우에는 그에 따른 옵션들을 다양하게 설정해 줄 수 있다.
특정 도메인 접근 허용
> const corsOptions = {
origin: 'http://example.com', // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가해줌
optionsSuccessStatus: 200 // 응답 상태 200으로 설정
};
app.use(cors(corsOptions));
특정 요청 접근 허용
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
});
만약 직접 구현한 서버가 없어 클라이언트 내에서만 해결을 해야한다면 어떤 방식을 사용할 수 있을까?

프록시란 ? 다양한 이유로 (주로 보안의 문제) 직접 통신하지 못하는 두 개의 컴퓨터 사이에서 서로 통신할 수 있도록 돕는 역할을 가리켜 프록시라 일컫는다.
클라이언트 - 서버 중간에서 요청을 받아 Access-Control-Allow-Origin헤더를 추가해주는 중간 다리 역할을 놔주는 방법으로 해결할 수 있다.
AJAX 요청이 프록시 서버를 건너며 Access-Control-* 설정이 되어 요청에 대한 응답을 받을 수 있었다.
이외에도 직접 proxy를 만들거나 JSONP 요청을 보내 스크립트 파일을 호출하여 우회.. 등 다양한 방법들이 있다.