2020년 2월 작성, 2026년 10월 보완. 원래 글의 PHP 코드에 있던 헤더 형식 오류를 고쳤고, PHP 8.2 이상에서 경고가 나는 문법을 바꿨다. OPTIONS 요청 처리와
Vary: Origin도 추가했다.
목차
목차 펼치기
요약
쿠키(로그인 세션)를 포함해서 다른 도메인의 PHP API를 Axios로 호출하려면 다음 다섯 가지가 모두 필요하다.
- Axios:
withCredentials: true - 서버:
Access-Control-Allow-Credentials: true - 서버:
Access-Control-Allow-Origin에*가 아니라 요청한 Origin을 정확히 적는다. - 서버: 브라우저가 먼저 보내는
OPTIONS(preflight) 요청에 2xx로 응답한다. - 서버: Origin마다 응답이 달라지면
Vary: Origin을 붙인다.
Cross-Origin이란
브라우저의 Same-Origin Policy는 스크립트가 실행되는 페이지와 호출하려는 주소의 프로토콜, 호스트, 포트가 모두 같을 때만 응답을 읽을 수 있게 한다. 이 셋 중 하나라도 다른 호출을 Cross-Origin(Cross Domain) 요청이라고 한다.
Cross-Origin 설정이 필요해진 이유
예전에는 서버가 HTML을 만들어 내려주는 방식(Server Side Rendering)이 주로 쓰였다. 지금은 프런트엔드와 백엔드를 나눠 개발하는 방식이 일반적이다. 앱 개발이 흔해지면서 웹도 하나의 클라이언트로 보게 되었고, 웹 서버와 API 서버의 도메인이 달라지면서 Cross-Origin 문제가 생긴다. 이를 서버에서 허용해 줘야 한다.
CORS 설정에 쓰는 헤더
백엔드는 응답 헤더로 CORS를 허용한다. 쿠키를 쓰지 않는 단순한 경우라면 아래 한 줄로 해결된다.
Access-Control-Allow-Origin: *
CORS 설정에 쓰는 주요 헤더는 다음과 같다.
Access-Control-Allow-OriginAccess-Control-Allow-CredentialsAccess-Control-Allow-MethodsAccess-Control-Allow-Headers
Axios 사용을 위한 PHP CORS 설정
1. Access-Control-Allow-Credentials: true
Axios는 기본적으로 Cross-Origin 요청에 쿠키를 보내지 않는다. 로그인 상태처럼 쿠키가 필요한 요청은 Axios에서 withCredentials 를 true 로 설정하고, 서버에서는 Access-Control-Allow-Credentials: true 로 응답해야 한다. 서버가 이 헤더를 주지 않으면 요청이 서버까지 가더라도 브라우저가 응답을 스크립트에 넘겨주지 않는다.
2. Access-Control-Allow-Origin: 요청한 Origin
withCredentials 를 true 로 쓰면 Access-Control-Allow-Origin: * 는 쓸 수 없다. 허용할 Origin을 정확히 적어야 하고, Origin에는 프로토콜까지 포함된다.
Access-Control-Allow-Origin: https://client.example.com
withCredentials 를 쓸 때는 Access-Control-Allow-Methods, Access-Control-Allow-Headers 에도 * 를 쓸 수 없으니 값을 직접 나열한다.
3. Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS
처음에는 맨 뒤의 OPTIONS 가 낯설었다. 브라우저는 JSON 본문을 보내거나 Authorization 같은 헤더를 붙인 요청을 보내기 전에, 서버가 그 요청을 허용하는지 OPTIONS 메서드로 먼저 확인한다. 이것을 preflight라고 한다. Axios가 보내는 것이 아니라 브라우저가 자동으로 보내는 요청이다.
4. Access-Control-Allow-Headers
Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization 처럼 클라이언트가 보내는 헤더를 허용한다. 실제로 쓰는 헤더만 서버에 맞춰 적으면 된다.
5. PHP 코드 모아 보기
<?php
header('Access-Control-Allow-Origin: https://client.example.com');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization');
header('Vary: Origin');
// preflight 요청은 헤더만 주고 바로 끝낸다
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
헤더 이름과 콜론 사이에는 공백을 넣지 않는다(Access-Control-Allow-Origin : 처럼 쓰면 안 된다).
허용할 Origin이 여러 개일 때
Access-Control-Allow-Origin 에는 Origin을 하나만 적을 수 있다. 쿠키를 쓰지 않으면 * 로 해결되지만, withCredentials 를 쓰면 * 를 쓸 수 없다. 그래서 요청한 Origin이 허용 목록에 있을 때 그 값을 그대로 돌려주도록 코드로 만든다.
<?php
$allowedOrigins = [
'http://localhost:3000', // 개발용 클라이언트
'https://client.example.com', // 운영용 클라이언트
];
$httpOrigin = $_SERVER['HTTP_ORIGIN'] ?? null;
if ($httpOrigin !== null && in_array($httpOrigin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: {$httpOrigin}");
header('Access-Control-Allow-Credentials: true');
}
// Origin마다 응답이 달라지므로 캐시가 섞이지 않게 한다
header('Vary: Origin');
트러블슈팅
설정했는데도 와일드카드(*) 관련 오류나 Access-Control-Allow-Origin 오류가 나는 경우
.htaccess 나 웹 서버 설정 파일에도 Access-Control-Allow-Origin 이 설정되어 있는지 확인한다. 웹 서버와 PHP가 둘 다 이 헤더를 보내면 값이 두 번 들어가고, 브라우저는 이를 오류로 처리한다. 한 곳에서만 설정해야 한다.
preflight 요청이 실패하는 경우
Access-Control-Allow-Methods에OPTIONS가 빠졌는지 확인한다.OPTIONS요청이 로그인 확인이나 라우터에 막혀 401·404·500 같은 응답을 받는지 확인한다. 위 코드처럼OPTIONS요청에는 헤더만 주고 바로 2xx로 응답해야 한다.withCredentials를 쓰는데Access-Control-Allow-Credentials: true가 빠지지 않았는지 확인한다.
로그인 상태가 유지되지 않는 경우
- Axios에서
withCredentials: true를 설정했는지 확인한다. Nuxt 2의@nuxtjs/axios모듈을 쓴다면nuxt.config.js의axios.credentials를true로 설정한다. - 세션 쿠키가 다른 사이트로 전송되려면 쿠키에
SameSite=None; Secure속성이 필요하고, 그러려면 HTTPS여야 한다.
정리
Express에서는 CORS 미들웨어로 쉽게 해결할 수 있었다. PHP에서 직접 설정해 보면서 Credentials를 쓸 때 와일드카드를 쓸 수 없다는 점처럼, 브라우저가 CORS를 얼마나 엄격하게 검사하는지 알게 되었다.
참고자료
- MDN: Cross-Origin Resource Sharing (CORS)
- MDN: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ’*’
- Axios: CORS error No ‘Access-Control-Allow-Origin’ header is present on the requested resource #569
- Axios: preflight request with OPTIONS method
- 다중 Domain 허용 PHP 코드 참고 링크