본문으로 건너뛰기
Kabkee.github.io
뒤로 가기

Axios withCredentials를 위한 PHP CORS 설정 (Preflight·다중 Origin)

2020년 2월 작성, 2026년 10월 보완. 원래 글의 PHP 코드에 있던 헤더 형식 오류를 고쳤고, PHP 8.2 이상에서 경고가 나는 문법을 바꿨다. OPTIONS 요청 처리와 Vary: Origin 도 추가했다.

목차

목차 펼치기

요약

쿠키(로그인 세션)를 포함해서 다른 도메인의 PHP API를 Axios로 호출하려면 다음 다섯 가지가 모두 필요하다.

  1. Axios: withCredentials: true
  2. 서버: Access-Control-Allow-Credentials: true
  3. 서버: Access-Control-Allow-Origin 에 * 가 아니라 요청한 Origin을 정확히 적는다.
  4. 서버: 브라우저가 먼저 보내는 OPTIONS(preflight) 요청에 2xx로 응답한다.
  5. 서버: 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 설정에 쓰는 주요 헤더는 다음과 같다.

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 요청이 실패하는 경우

로그인 상태가 유지되지 않는 경우

정리

Express에서는 CORS 미들웨어로 쉽게 해결할 수 있었다. PHP에서 직접 설정해 보면서 Credentials를 쓸 때 와일드카드를 쓸 수 없다는 점처럼, 브라우저가 CORS를 얼마나 엄격하게 검사하는지 알게 되었다.

참고자료


이 글 공유하기:

이전 글
Azure App Service 시간대(Timezone) 바꾸기: Linux는 TZ, Windows는 WEBSITE_TIME_ZONE
다음 글
Vue 2 + BootstrapVue: Bootstrap CSS를 scoped로 격리하기 (::v-deep + SCSS)