$ curl -X POST https://example.com/wp-json/wp/v2/posts \
-H "Content-Type: application/json" \
-d '{"title":"테스트 포스트","status":"draft"}'
HTTP/1.1 401 Unauthorized
{
"code": "rest_cannot_create",
"message": "Sorry, you are not allowed to create posts as this user.",
"data": {
"status": 401
}
}
WordPress REST API를 처음 연동할 때 거의 100% 마주치는 응답이다. 나도 자동 포스팅 스크립트를 처음 만들던 날, 이 에러 메시지를 보고 한 시간 넘게 삽질했다. 분명 관리자 계정인데 왜 권한이 없다고 하는 건지, 패스워드를 세 번이나 다시 입력했는데도 같은 결과만 반복됐거든. 결론부터 말하면, WordPress REST API는 일반 로그인 비밀번호가 아니라 별도의 Application Password를 요구한다. 여기서부터 하나씩 풀어보겠다.
📑 목차
401 응답 구조 정확히 읽기
HTTP 401 Unauthorized는 “인증 정보가 없거나 잘못됐다”는 뜻이다. 403 Forbidden과 혼동하기 쉬운데, 403은 인증은 됐지만 권한이 부족한 경우고, 401은 애초에 누구인지 확인조차 안 된 상태이다. WordPress REST API가 반환하는 401 응답에는 몇 가지 변형이 있다.
// 인증 헤더 자체가 없을 때
{
"code": "rest_not_logged_in",
"message": "You are not currently logged in.",
"data": { "status": 401 }
}
// 인증 헤더는 있지만 자격증명이 틀렸을 때
{
"code": "rest_cannot_create",
"message": "Sorry, you are not allowed to create posts as this user.",
"data": { "status": 401 }
}
// Application Password가 비활성화된 서버
{
"code": "rest_forbidden",
"message": "Application passwords are not available.",
"data": { "status": 401 }
}
code 필드를 주의 깊게 봐야 한다. rest_not_logged_in이면 Authorization 헤더가 아예 빠졌거나 서버가 헤더를 무시하고 있는 것이고, rest_cannot_create면 인증은 시도했지만 유저명이나 패스워드가 맞지 않는 경우이다. 이 차이를 구분해야 삽질 시간을 줄일 수 있다.
원인 분석: 왜 401이 뜨는가
WordPress REST API에서 401 에러가 발생하는 원인은 생각보다 다양하다. 내 경험상 가장 빈번한 것부터 나열하면 이렇다.
일반 로그인 비밀번호를 그대로 사용
WordPress 5.6부터 도입된 Application Passwords 기능은 REST API 전용 인증 수단이다. wp-admin에 로그인할 때 쓰는 비밀번호로는 API 인증이 되지 않는다. 처음엔 이게 직관적이지 않아서 당혹스러웠는데, 보안 관점에서 보면 합리적이다. API용 비밀번호를 별도로 관리하면 유출 시 해당 키만 폐기하면 되니까.
Basic Auth 헤더가 서버에 도달하지 않음
Apache 서버에서 흔히 발생하는 문제이다. .htaccess에 특별한 설정이 없으면 Authorization 헤더를 PHP까지 전달하지 않는다. 이 경우 WordPress는 인증 헤더를 아예 받지 못하기 때문에 rest_not_logged_in 에러를 반환한다. Nginx를 쓴다면 보통 이 문제가 없지만, Apache + mod_php 조합에서는 거의 반드시 겪게 된다.
보안 플러그인의 간섭
Wordfence, iThemes Security, Sucuri 같은 보안 플러그인이 REST API 접근 자체를 차단하는 경우가 있다. 특히 Wordfence의 “Disable WordPress application passwords” 옵션이 켜져 있으면 Application Password로 인증해도 무조건 401을 반환한다. 나는 이걸 모르고 패스워드를 다섯 번이나 새로 만들었는데, 결국 Wordfence 설정 하나가 원인이었다. 그때의 허탈함이란.
HTTPS가 적용되지 않은 환경
WordPress 5.6 이상에서 Application Passwords는 기본적으로 HTTPS 환경에서만 작동한다. 로컬 개발 환경이나 HTTP로만 접근 가능한 서버에서는 Application Password 자체가 비활성화되어 인증 시도가 무시된다. 로컬에서 테스트할 때 이 조건을 우회하려면 wp-config.php에 한 줄을 추가해야 한다.
// wp-config.php — 로컬 개발 환경에서만 사용할 것
define( 'WP_ENVIRONMENT_TYPE', 'local' );
이 설정을 추가하면 HTTP 환경에서도 Application Passwords가 활성화된다. 운영 서버에서는 절대 이 설정을 사용하면 안 된다.
Application Password 생성하기
WordPress 관리자 대시보드에서 사용자 → 프로필 페이지 하단에 “Application Passwords” 섹션이 있다. 새 애플리케이션 이름(예: “Blog Auto Post Script”)을 입력하고 “Add New Application Password” 버튼을 클릭하면 24자리 비밀번호가 생성된다.
중요한 점이 두 가지 있다. 생성된 비밀번호는 화면에 한 번만 표시되고 이후 다시 볼 수 없으므로 반드시 복사해두어야 한다. 그리고 화면에 표시될 때 4자리씩 공백으로 구분되어 보이는데(abcd efgh ijkl mnop qrst uvwx 형태), 실제 API 호출 시에는 이 공백을 포함하든 제거하든 둘 다 작동한다. WordPress가 내부적으로 공백을 제거하고 처리하기 때문이다. 그래도 깔끔하게 공백을 제거하고 쓰는 쪽을 권한다.
Base64 인코딩과 올바른 요청 보내기
Application Password를 받았으면 이제 HTTP Basic Authentication 형식으로 요청을 보내면 된다. 사용자명:Application_Password 문자열을 Base64로 인코딩한 뒤 Authorization: Basic {인코딩된문자열} 헤더에 넣는 구조이다.
# 1. Base64 인코딩 (macOS/Linux)
$ echo -n "admin:abcdefghijklmnopqrstuvwx" | base64
YWRtaW46YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4
# 2. curl로 포스트 생성 요청
$ curl -X POST https://example.com/wp-json/wp/v2/posts \
-H "Authorization: Basic YWRtaW46YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4" \
-H "Content-Type: application/json" \
-d '{
"title": "자동 생성 포스트",
"content": "<p>REST API로 작성된 글입니다.</p>",
"status": "draft"
}'
# 3. 성공 응답 (HTTP 201 Created)
HTTP/1.1 201 Created
{
"id": 142,
"title": { "rendered": "자동 생성 포스트" },
"status": "draft",
"link": "https://example.com/?p=142"
...
}
echo -n에서 -n 옵션을 빼먹으면 줄바꿈 문자(\n)까지 Base64에 포함되어 인증이 실패한다. 사소하지만 이것 때문에 디버깅에 시간을 쏟는 사람이 의외로 많다.
Apache 서버에서 Authorization 헤더가 전달되지 않는 문제가 있다면 .htaccess 파일에 다음을 추가하자.
# .htaccess — WordPress 설치 디렉토리
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# 또는 CGI/FastCGI 환경이라면
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
이 설정 없이는 Apache가 Authorization 헤더를 삼켜버리기 때문에, 아무리 올바른 자격증명을 보내도 WordPress 쪽에서는 비인증 요청으로 처리한다.
JavaScript fetch API 예제
프론트엔드나 Node.js 환경에서 WordPress REST API를 호출하는 경우도 많다. JavaScript의 fetch를 사용한 예제이다.
const username = 'admin';
const appPassword = 'abcdefghijklmnopqrstuvwx';
const credentials = btoa(`${username}:${appPassword}`);
const response = await fetch('https://example.com/wp-json/wp/v2/posts', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '자동 생성 포스트',
content: '<p>fetch API로 작성된 글입니다.</p>',
status: 'draft',
}),
});
if (!response.ok) {
const error = await response.json();
console.error(`HTTP ${response.status}: ${error.code} — ${error.message}`);
// HTTP 401: rest_not_logged_in — You are not currently logged in.
throw new Error('WordPress API 인증 실패');
}
const post = await response.json();
console.log(`포스트 생성 완료: ID ${post.id}`);
Node.js 환경에서는 btoa 대신 Buffer.from()을 사용한다.
// Node.js 18+
const credentials = Buffer.from(`${username}:${appPassword}`).toString('base64');
브라우저에서 직접 호출하는 경우 CORS 문제도 함께 발생할 수 있다. 이때는 WordPress 서버 쪽에서 Access-Control-Allow-Headers에 Authorization을 포함해야 한다. 다만 브라우저에서 직접 API 키를 노출하는 건 보안상 권장하지 않으므로, 서버 사이드에서 호출하는 방식을 추천한다.
Postman으로 빠르게 테스트하기
코드를 작성하기 전에 Postman으로 먼저 API 연결을 확인하면 디버깅 시간을 크게 줄일 수 있다. Postman에서 테스트하는 순서는 간단하다.
- 새 요청(Request)을 만들고 메서드를
GET, URL을https://사이트주소/wp-json/wp/v2/users/me로 설정한다. - Authorization 탭에서 Type을 Basic Auth로 선택한다.
- Username에 WordPress 사용자명, Password에 Application Password를 입력한다.
- Send를 클릭한다.
정상적으로 인증되면 현재 사용자의 정보가 JSON으로 반환된다. /users/me 엔드포인트는 인증 상태만 확인하는 데 최적이다. 이 요청이 200을 반환하면 인증 자체는 성공한 것이므로, 이후 포스트 생성이나 수정에서 문제가 생기면 권한(Role) 쪽을 살펴보면 된다.
Postman에서 Authorization 탭을 쓰면 Base64 인코딩을 자동으로 해주기 때문에 인코딩 실수를 배제할 수 있다. curl로는 잘 되는데 코드에서 안 되는 상황이라면, 인코딩 과정에서 문제가 생긴 것이므로 Postman 결과와 코드의 Authorization 헤더 값을 직접 비교해보자.
흔한 실수 모음
401 에러를 해결하는 과정에서 자주 발견되는 실수들을 정리했다.
사용자명(username) vs 이메일(email) 혼동
Basic Auth에서 사용하는 것은 WordPress 사용자명(username)이다. 이메일 주소가 아니다. wp-admin 로그인 화면에서 이메일로도 로그인할 수 있다 보니 혼동하기 쉬운데, REST API Basic Auth에서 이메일을 넣으면 인증이 실패한다. 관리자 대시보드 → 사용자 → 프로필 페이지 상단의 “사용자명” 필드를 확인하자.
Application Password의 공백 처리
앞서 언급했듯이 WordPress가 자동으로 공백을 제거해주지만, Base64 인코딩 전에 수동으로 공백을 넣거나 빼는 과정에서 오타가 발생할 수 있다. 가장 안전한 방법은 생성 직후 공백 없이 한 줄로 복사해두는 것이다.
멀티사이트(Multisite) 환경의 추가 설정
WordPress Multisite에서는 Application Passwords가 기본적으로 비활성화되어 있을 수 있다. wp-config.php에 아래 필터를 추가해야 한다.
// wp-config.php (Multisite 환경)
add_filter( 'wp_is_application_passwords_available_for_user', '__return_true' );
Application Password 재발급 시 이전 키 폐기
Application Password는 여러 개를 동시에 활성화할 수 있다. 문제 해결 과정에서 비밀번호를 여러 번 새로 만들었다면, 사용하지 않는 이전 키는 반드시 폐기(Revoke)하자. 사용하지 않는 API 키를 방치하는 건 보안상 좋지 않다.
REST API 자체가 비활성화된 경우
일부 보안 가이드에서 REST API를 완전히 비활성화하라고 권하는 경우가 있다. functions.php에 add_filter('rest_authentication_errors', ...) 코드가 들어가 있거나, “Disable WP REST API” 같은 플러그인이 설치되어 있으면 모든 API 요청이 401 또는 403을 반환한다. 이 경우 인증 문제가 아니라 REST API 접근 자체가 막힌 것이므로, 해당 코드나 플러그인을 먼저 확인해야 한다.
정리하면, 401 에러의 원인은 크게 세 가지로 수렴한다. 잘못된 자격증명, 서버가 인증 헤더를 전달하지 않는 환경 문제, 그리고 플러그인이나 설정에 의한 API 접근 차단. 이 세 가지를 순서대로 점검하면 대부분의 경우 원인을 찾을 수 있다. 나도 처음엔 막막했지만 한번 구조를 이해하고 나니 이후로는 5분 안에 해결하게 되더라.