WordPress Application Passwords 생성 및 REST API 연동 가이드 (2026)

Application Passwords는 WordPress 5.6부터 도입된 REST API 인증 방식이다. 기존에는 쿠키 인증이나 OAuth 플러그인 없이는 외부에서 WordPress API를 호출하기가 꽤 번거로웠는데, 이 기능 덕분에 별도 플러그인 설치 없이도 HTTP Basic Auth 기반의 API 연동이 가능해졌다. 나는 자동 포스팅 시스템을 구축하면서 이 기능을 처음 사용했는데, 세팅 자체는 5분이면 끝나지만 HTTPS 문제나 플러그인 충돌 때문에 예상보다 시간을 잡아먹은 경험이 있다. 이 글에서는 Application Passwords의 동작 원리부터 실제 API 연동 코드, 그리고 자주 발생하는 문제들의 해결법까지 한 번에 정리한다.

Application Passwords란 무엇인가

Application Passwords는 WordPress 사용자 계정에 연결된 별도의 비밀번호이다. 일반 로그인 비밀번호와 완전히 분리되어 있고, REST API 요청 시 HTTP Basic Authentication 헤더에 실어 보내는 용도로만 사용된다. WordPress 5.6(2020년 12월 릴리스)에서 코어에 포함되었기 때문에 별도의 플러그인이 필요하지 않는다.

핵심 특징을 정리하면 다음과 같다.

  • 사용자 단위 발급 — 각 WordPress 사용자가 자신의 프로필 페이지에서 직접 생성한다.
  • 애플리케이션 단위 관리 — 하나의 사용자 계정에 여러 개의 Application Password를 만들 수 있고, 이름을 붙여 구분한다. 자동 포스팅 봇용, 모니터링 스크립트용 등으로 나눠서 관리하는 게 좋다.
  • 개별 폐기 가능 — 특정 Application Password만 골라서 삭제(revoke)할 수 있으므로, 하나가 노출되더라도 나머지에 영향을 주지 않는다.
  • 일반 로그인에는 사용 불가 — wp-login.php에서는 Application Password로 로그인할 수 없다. API 전용이다.

생성된 비밀번호는 24자리 영문 소문자로 구성되며, 4자리씩 공백으로 구분된 형태(abcd efgh ijkl mnop qrst uvwx)로 표시된다. 실제 API 호출 시에는 공백을 제거하고 사용해도 되고, 공백을 포함해도 WordPress가 알아서 처리한다.

기존 인증 방식과의 비교

WordPress REST API에서 인증을 처리하는 방법은 크게 세 가지이다. Application Passwords가 나오기 전에는 나머지 두 가지에 의존해야 했고, 각각 뚜렷한 한계가 있었다.

방식 플러그인 필요 외부 서버에서 사용 비밀번호 노출 위험 세분화된 폐기
쿠키 인증 (Nonce) 아니오 불가 낮음 불가
OAuth 1.0a / 2.0 가능 낮음 가능
Application Passwords 아니오 (5.6+) 가능 중간 (HTTPS 필수) 가능

쿠키 인증은 WordPress 프론트엔드 내부에서만 동작한다. 테마나 플러그인 내부의 AJAX 요청에는 적합하지만, 외부 Python 스크립트나 CI/CD 파이프라인에서 API를 호출하려면 사용할 수 없다. OAuth는 외부 연동에 적합하지만 별도 플러그인(WP OAuth Server 등)을 설치하고 클라이언트 등록 과정을 거쳐야 해서 세팅이 복잡하다.

Application Passwords는 이 둘 사이의 빈자리를 채워준다. 플러그인 없이 외부 서버에서 API를 호출할 수 있고, 설정도 단순하다. 다만 HTTP Basic Auth 특성상 요청마다 비밀번호가 헤더에 포함되므로 반드시 HTTPS 환경에서 사용해야 한다. 이 부분은 뒤의 보안 섹션에서 다시 다루겠다.

wp-admin에서 Application Password 생성하기

생성 과정 자체는 매우 단순하다. WordPress 관리자 화면에서 클릭 몇 번이면 된다.

단계별 생성 절차

  1. WordPress 관리자 대시보드에 로그인한다.
  2. 사용자(Users) → 프로필(Profile) 메뉴로 이동한다.
  3. 페이지 하단까지 스크롤하면 “Application Passwords” 섹션이 보인다.
  4. “New Application Password Name” 입력 필드에 용도를 나타내는 이름을 입력한다. 예를 들어 auto-posting-bot 또는 monitoring-script 같은 이름이 적절하다.
  5. “Add New Application Password” 버튼을 클릭한다.
  6. 24자리 비밀번호가 화면에 한 번만 표시된다. 이 순간을 놓치면 다시 확인할 방법이 없으므로 반드시 복사해서 안전한 곳에 저장하자.

내가 처음 이걸 만들었을 때 비밀번호를 복사하지 않고 페이지를 닫아버린 적이 있다. 다시 확인할 수 있는 방법은 없고, 해당 비밀번호를 삭제한 뒤 새로 만드는 수밖에 없었다. 비밀번호 관리자(1Password, Bitwarden 등)에 바로 저장하는 습관을 들이는 걸 추천한다.

Application Passwords 섹션이 안 보일 때

프로필 페이지에 Application Passwords 섹션 자체가 없는 경우가 간혹 있다. 원인은 대부분 두 가지 중 하나이다.

  • HTTPS가 설정되지 않은 경우 — WordPress는 보안상의 이유로 HTTPS가 아닌 환경에서 Application Passwords UI를 숨긴다. localhost 환경은 예외적으로 허용된다.
  • 보안 플러그인이 비활성화한 경우 — Wordfence, iThemes Security 같은 보안 플러그인이 Application Passwords 기능을 의도적으로 꺼놓을 수 있다. 플러그인 설정에서 해당 옵션을 확인하자.

HTTPS 없이 강제로 활성화해야 하는 개발 환경이라면 wp-config.php에 다음 필터를 추가하는 방법도 있지만, 프로덕션에서는 절대 사용하지 말자.

// wp-config.php — 개발 환경 전용, 프로덕션 사용 금지
add_filter( 'wp_is_application_passwords_available', '__return_true' );

curl로 REST API 호출하기

Application Password를 생성했으면 바로 API 호출을 테스트할 수 있다. 가장 빠른 방법은 터미널에서 curl을 쓰는 것이다.

# 글 목록 조회 (인증 불필요)
curl -s https://your-site.com/wp-json/wp/v2/posts | jq '.[0].title'

# 새 글 발행 (인증 필요)
curl -X POST https://your-site.com/wp-json/wp/v2/posts \
  -u "admin:abcd efgh ijkl mnop qrst uvwx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "curl로 작성한 테스트 포스트",
    "content": "<p>Application Passwords를 이용한 자동 포스팅 테스트입니다.</p>",
    "status": "draft"
  }'

# 특정 글 수정
curl -X PUT https://your-site.com/wp-json/wp/v2/posts/42 \
  -u "admin:abcd efgh ijkl mnop qrst uvwx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "수정된 제목"
  }'

# 미디어 업로드
curl -X POST https://your-site.com/wp-json/wp/v2/media \
  -u "admin:abcd efgh ijkl mnop qrst uvwx" \
  -H "Content-Disposition: attachment; filename=photo.jpg" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

-u 옵션에 사용자명:Application Password 형태로 넘기면 curl이 자동으로 Base64 인코딩하여 Authorization: Basic ... 헤더를 생성한다. 비밀번호의 공백은 포함해도 되고 제거해도 상관없다. WordPress 내부에서 공백을 무시하고 처리한다.

테스트할 때 주의할 점이 하나 있다. statuspublish로 설정하면 즉시 공개되므로, 테스트 중에는 draft로 두는 것이 안전하다. 나도 한번 실수로 테스트 글을 바로 공개해버린 적이 있어서 이후로는 항상 draft 상태로 먼저 확인한다.

JavaScript fetch로 연동하기

Node.js 환경이나 브라우저(CORS 설정 필요)에서 JavaScript fetch API로 연동하는 방법이다. Base64 인코딩을 직접 처리해야 한다.

// Node.js 18+ (전역 fetch 사용 가능)
const WP_URL = 'https://your-site.com/wp-json/wp/v2';
const USERNAME = 'admin';
const APP_PASSWORD = 'abcdefghijklmnopqrstuvwx'; // 공백 제거된 24자리

// Base64 인코딩
const credentials = Buffer.from(`${USERNAME}:${APP_PASSWORD}`).toString('base64');

// 새 글 발행
async function createPost(title, content, status = 'draft') {
  const response = await fetch(`${WP_URL}/posts`, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ title, content, status }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${response.status}: ${error.message}`);
  }

  return response.json();
}

// 카테고리 목록 조회
async function getCategories() {
  const response = await fetch(`${WP_URL}/categories`, {
    headers: { 'Authorization': `Basic ${credentials}` },
  });
  return response.json();
}

// 사용 예시
const post = await createPost(
  'JavaScript에서 작성한 포스트',
  '<p>fetch API로 WordPress에 글을 발행합니다.</p>',
  'draft'
);
console.log(`생성된 글 ID: ${post.id}, 링크: ${post.link}`);

Node.js 18 미만 버전을 사용 중이라면 node-fetch 패키지를 설치하거나, axios를 사용하자. axios의 경우 auth 옵션에 { username, password } 객체를 넘기면 Base64 인코딩을 자동으로 처리해 준다.

Python requests로 연동하기

Python에서는 requests 라이브러리의 auth 파라미터를 사용하면 간결하게 처리할 수 있다. 나는 자동 포스팅 스크립트를 Python으로 작성했는데, requests의 인터페이스가 워낙 직관적이라 구현이 빠르다.

import requests
import os
from pathlib import Path

WP_URL = "https://your-site.com/wp-json/wp/v2"
USERNAME = os.environ.get("WP_USERNAME", "admin")
APP_PASSWORD = os.environ.get("WP_APP_PASSWORD")  # 환경 변수에서 읽기

auth = (USERNAME, APP_PASSWORD)

# 새 글 발행
def create_post(title: str, content: str, category_ids: list[int] = None, status: str = "draft") -> dict:
    payload = {
        "title": title,
        "content": content,
        "status": status,
    }
    if category_ids:
        payload["categories"] = category_ids

    resp = requests.post(f"{WP_URL}/posts", json=payload, auth=auth, timeout=30)
    resp.raise_for_status()
    return resp.json()

# 이미지 업로드 후 글에 첨부
def upload_media(file_path: str) -> dict:
    path = Path(file_path)
    mime_types = {".jpg": "image/jpeg", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp"}
    content_type = mime_types.get(path.suffix.lower(), "application/octet-stream")

    headers = {
        "Content-Disposition": f'attachment; filename="{path.name}"',
        "Content-Type": content_type,
    }
    with open(file_path, "rb") as f:
        resp = requests.post(f"{WP_URL}/media", headers=headers, data=f, auth=auth, timeout=60)

    resp.raise_for_status()
    return resp.json()

# 사용 예시
if __name__ == "__main__":
    post = create_post(
        title="Python 자동 포스팅 테스트",
        content="<p>requests 라이브러리로 WordPress REST API에 글을 발행합니다.</p>",
        category_ids=[3, 7],
        status="draft",
    )
    print(f"생성 완료 — ID: {post['id']}, URL: {post['link']}")

비밀번호를 코드에 직접 넣지 않고 환경 변수(WP_APP_PASSWORD)에서 읽는 패턴을 사용했다. CI/CD 파이프라인에서는 GitHub Secrets나 환경 변수 설정을 통해 주입하면 된다. .env 파일에 저장할 때도 .gitignore에 반드시 추가해야 한다.

보안 주의사항

Application Passwords는 편리하지만, HTTP Basic Auth 기반이라는 태생적 한계가 있다. 올바르게 사용하지 않으면 보안 사고로 이어질 수 있으므로 아래 사항을 반드시 지켜야 한다.

HTTPS는 선택이 아닌 필수

HTTP Basic Auth는 비밀번호를 Base64로 인코딩할 뿐 암호화하지 않는다. HTTPS 없이 사용하면 네트워크를 지나가는 모든 요청에서 비밀번호가 평문으로 노출된다. WordPress도 이 위험을 인지하고 있어서, HTTPS가 아닌 환경에서는 Application Passwords 기능 자체를 비활성화한다.

Let’s Encrypt로 무료 SSL 인증서를 발급받거나, Cloudflare의 Flexible SSL을 사용하면 HTTPS 적용이 어렵지 않다. 2026년 현재 HTTPS가 적용되지 않은 사이트를 운영하는 것 자체가 SEO와 보안 측면에서 불이익이 크므로, 아직 적용하지 않았다면 이 기회에 설정하는 것을 권한다.

비밀번호 관리 원칙

  • 용도별로 분리 생성 — 자동 포스팅 봇, 모니터링 스크립트, 외부 서비스 연동 등 각각 별도의 Application Password를 만들자. 하나가 유출되었을 때 해당 비밀번호만 폐기하면 나머지 시스템에는 영향이 없다.
  • 주기적으로 폐기 후 재생성 — 사용하지 않는 Application Password가 있다면 바로 삭제하자. 프로필 페이지의 Application Passwords 목록에서 “Revoke” 버튼으로 개별 삭제가 가능하다.
  • 코드에 직접 입력 금지 — 환경 변수, 시크릿 매니저, 또는 CI/CD의 시크릿 저장소를 사용하자. Git 히스토리에 한 번이라도 비밀번호가 남으면 제거하기가 매우 번거롭다.
  • 최소 권한 사용자 계정 사용 — Administrator 계정 대신, API 용도에 맞는 역할(Author, Editor)의 계정을 따로 만들어서 Application Password를 발급하면 피해 범위를 줄일 수 있다.

트러블슈팅: 자주 발생하는 문제들

401 Unauthorized 응답

API 호출 시 401 에러가 가장 흔하다. 원인별로 확인 순서를 정리하면 다음과 같다.

  1. 사용자명 확인 — WordPress 로그인 아이디(username)를 사용해야 한다. 이메일 주소가 아니다. wp-admin → 사용자 → 프로필에서 “사용자명” 필드를 확인하자.
  2. 비밀번호 복사 오류 — Application Password에 공백이 포함된 채 복사했거나, 앞뒤에 줄바꿈 문자가 포함되었을 수 있다. 터미널에서 echo -n "password" | xxd로 바이트를 확인해 보자.
  3. Authorization 헤더 누락 — 일부 서버 환경(Apache + CGI/FastCGI)에서는 Authorization 헤더가 PHP까지 전달되지 않는다. .htaccess에 다음을 추가해야 한다.
# .htaccess — Authorization 헤더 전달
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]

# 또는 CGI 환경이라면
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

REST API 자체가 비활성화된 경우

일부 보안 플러그인은 비로그인 사용자의 REST API 접근을 차단한다. Wordfence의 경우 Firewall → Advanced Firewall Options에서 REST API 관련 설정을 확인하자. Disable REST API 같은 플러그인을 사용 중이라면 해당 플러그인이 인증된 요청까지 차단하고 있을 수 있다.

REST API 접근 여부를 빠르게 확인하려면 브라우저에서 https://your-site.com/wp-json/에 접속해 보자. JSON 응답이 돌아오면 API는 활성화되어 있는 것이다. 403이나 빈 응답이 돌아온다면 플러그인이나 서버 설정에서 차단하고 있을 가능성이 높다.

플러그인 충돌 진단

Application Passwords가 정상 동작하지 않을 때 플러그인 충돌을 의심해야 하는 경우가 있다. 특히 Two-Factor Authentication 플러그인이나 커스텀 로그인 플러그인이 인증 흐름을 변경하면서 Application Passwords를 무력화시키는 사례를 몇 번 봤다.

진단 방법은 단순하지만 효과적이다.

  1. 모든 플러그인을 비활성화한다.
  2. Application Passwords로 API 호출을 테스트한다.
  3. 정상 동작한다면 플러그인을 하나씩 활성화하면서 어떤 플러그인에서 문제가 재현되는지 찾다.

SSH 접근이 가능한 환경이라면 WP-CLI로 더 빠르게 진단할 수 있다.

# 전체 플러그인 비활성화
wp plugin deactivate --all

# API 테스트
curl -u "admin:abcdefghijklmnopqrstuvwx" \
  https://your-site.com/wp-json/wp/v2/users/me

# 플러그인 하나씩 활성화하며 테스트
wp plugin activate wordfence
curl -u "admin:abcdefghijklmnopqrstuvwx" \
  https://your-site.com/wp-json/wp/v2/users/me

wp plugin activate two-factor
curl -u "admin:abcdefghijklmnopqrstuvwx" \
  https://your-site.com/wp-json/wp/v2/users/me

Nginx 리버스 프록시 환경에서의 문제

Nginx를 리버스 프록시로 사용하는 환경에서는 Authorization 헤더가 백엔드(PHP-FPM)로 전달되지 않는 경우가 있다. Nginx 설정에서 다음 항목이 포함되어 있는지 확인하자.

location / {
    # ... 기존 설정 ...

    # Authorization 헤더를 백엔드로 전달
    proxy_set_header Authorization $http_authorization;
    # 또는 FastCGI 환경이라면
    fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}

이 설정이 빠져 있으면 WordPress가 인증 정보를 전혀 받지 못하므로 항상 401을 반환한다. 경험상 Docker 기반 WordPress 환경에서 이 문제를 가장 많이 마주쳤다.