REST 라우트를 만들고 나면 “누가 부르는가” 를 정해야 합니다. 워드프레스에서 실질적인 선택지는 셋이고, 셋은 편의의 차이가 아니라 지켜야 할 비밀이 어디에 놓이는가의 차이입니다.
쿠키와 nonce — 브라우저가 부를 때
로그인한 사용자의 브라우저에서 같은 사이트의 API 를 부를 때 쓰는 방식입니다. 인증은 쿠키가 하고, nonce 는 인증이 아니라 CSRF 방어입니다. 둘의 역할을 섞으면 판단이 어긋납니다 — nonce 는 비밀이 아니고, 페이지 소스에 그대로 실려도 되는 값입니다.
wp_enqueue_script( 'wper-app', $url, [], $ver, true );
wp_localize_script(
'wper-app',
'wperApi',
[
'root' => esc_url_raw( rest_url( 'wper/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
]
);
// 클라이언트: fetch( wperApi.root + 'orders/12', {
// credentials: 'same-origin',
// headers: { 'X-WP-Nonce': wperApi.nonce }
// } )
이 방식의 함정은 nonce 가 만료된다는 것입니다. 하루쯤 열어 둔 탭에서 저장을 누르면 rest_cookie_invalid_nonce 로 403 이 돌아옵니다. 사용자 입장에서는 “방금까지 되던 것이 안 된다” 이므로, 클라이언트가 이 오류를 잡아 새로고침을 안내하거나 하트비트로 nonce 를 갱신해야 합니다.
위협 모델은 명확합니다. 비밀이 브라우저 세션이므로 XSS 가 성립하면 그대로 끝입니다. 그래서 이 방식을 쓰는 화면일수록 출력 이스케이프가 중요해집니다.
애플리케이션 비밀번호 — 서버가 부를 때
다른 서버나 스크립트가 우리 API 를 부를 때는 쿠키가 없습니다. 코어의 애플리케이션 비밀번호가 이 자리를 위해 있습니다. 사용자 프로필에서 용도별로 발급하고, HTTP Basic 으로 보내며, 개별로 폐기할 수 있습니다.
curl -u 'ci-bot:abcd EFGH ijkl MNOP qrst UVWX'
https://example.com/wp-json/wp/v2/posts?status=draft
중요한 성질이 셋 있습니다. 첫째, 사용자의 권한을 넘지 못합니다 — 그러니 자동화 전용 사용자를 만들고 필요한 최소 역할만 줍니다. 둘째, 비밀번호 자체와 분리돼 있어 유출 시 그 하나만 폐기하면 됩니다. 셋째, HTTPS 가 전제입니다. Basic 인증은 자격증명을 헤더에 그대로 싣습니다.
위협 모델은 “설정 파일에 오래 살아 있는 자격증명” 입니다. 저장소에 커밋되지 않게 하고, 회전 절차를 미리 정해 두는 것이 이 방식의 숙제입니다.
토큰 — 제3자가 발급할 때
모바일 앱이나 별도 프런트엔드가 자기 사용자를 대신해 부르는 경우, 또는 만료가 짧은 자격증명이 필요한 경우에 토큰을 씁니다. 코어 기능이 아니므로 플러그인이나 자체 구현이 붙고, 그 순간 발급자와 갱신 흐름을 우리가 운영하게 됩니다.
여기서 정직할 필요가 있습니다. 토큰이 더 현대적이라서 더 안전한 것이 아닙니다. 짧은 만료는 유출의 창을 줄여 주지만, 대신 갱신 토큰 보관 · 폐기 목록 · 키 회전이라는 새 운영 부담이 생깁니다. 서버 하나가 야간 배치로 글을 올리는 정도라면 애플리케이션 비밀번호가 정답이고, 토큰은 과합니다.
보안 설정 전반의 관점은 보안 아카이브에 있고, 인증 · 권한 구성을 포함한 점검을 한 번에 받고 싶다면 최적화 지원 사업에 진단이 포함돼 있습니다.
다음 회차
위 그림의 네 번째 줄이 다음 회차입니다. 외부 서비스가 우리를 부르는 웹훅에는 쿠키도 nonce 도 없어서, nonce 검증을 걸면 100% 실패합니다. 그럼 무엇으로 검증하는지 다룹니다.