WordPress REST API の認証を通す — Application Passwords と、401が返るときの切り分け
先に結論
WordPress の REST API に認証を通す方法は、昔はプラグインを選ぶところから始まりました。 いまは選ぶ必要がありません。用途が2つに分かれていて、それぞれ答えが1つです。
| 呼び出し元 | 使うもの | プラグイン |
|---|---|---|
| サーバー・スクリプト・外部アプリ(cron、CLI、別サーバー) | Application Passwords(Basic認証) | 不要。WordPress 5.6からコア同梱 |
| 同じサイトのブラウザ内(管理画面のJS、テーマ、プラグイン) | Cookie + nonce(X-WP-Nonce) | 不要 |
| 上記以外で、どうしても必要な場合 | OAuth 1.0a / JWT のプラグイン | 必要。ただし先に上2つを検討する |
そして、401が返ったときの切り分けはこうです。
応答の code | 意味 | 疑うところ |
|---|---|---|
rest_forbidden | 権限がない | 認証は通っているが、そのユーザーに権限がない |
rest_not_logged_in | 未認証として扱われている | Authorizationヘッダが届いていない(詳しく) |
incorrect_password | パスワードが違う | ログインパスワードを使っていないか |
application_passwords_disabled | 機能が無効 | セキュリティプラグイン、またはHTTPSでない |
rest_no_route(404) | ルートが見つからない | URLかメソッド違い。エンドポイントが塞がれている場合も |
検索すると「OAuth 2 のプラグインを入れる」という解説が出てきますが、 WordPress公式が認証プラグインとして挙げているのは OAuth 1.0a と JWT で、OAuth 2 ではありません。 コアが用意したのは Application Passwords です。 いまから OAuth 2 のサーバーを立てる必要は、ほぼありません。
選択肢は実質2つしかない
Application Passwords はコアの機能
公式ドキュメントにはこう書かれています。 「5.6 以降、WordPress には Application Passwords が同梱されており、 ユーザー編集画面(wp-admin → ユーザー → ユーザーを編集)から生成できます」。
発行したものは HTTPS 経由の Basic認証(RFC 7617)でREST APIに渡します。 プラグインのインストールも、Client ID の取得も、有料ライセンスも要りません。
認証プラグインを入れる話は、ほぼ終わった
公式ドキュメントは認証プラグインとして OAuth 1.0a Server と JSON Web Tokens を挙げています。 そして Basic Authentication プラグインについては、はっきりこう書いています。
「このプラグインはリクエストごとにユーザー名とパスワードの送信を必要とするため、
開発とテストでのみ使用すべきであり、本番環境では使うべきではありません。
上記の Application Passwords を使うほうが望ましいです」
— Authentication(REST API Handbook・要約)
| 手段 | コアに入っているか | 公式の位置づけ |
|---|---|---|
| Cookie + nonce | 入っている | プラグイン・テーマ開発での推奨 |
| Application Passwords | 入っている(5.6〜) | 外部アプリからの推奨 |
| OAuth 1.0a Server | プラグイン | 代替手段として紹介 |
| JSON Web Tokens | プラグイン | 代替手段として紹介 |
| Basic Authentication プラグイン | プラグイン | 開発・テスト専用。本番で使うな |
| OAuth 2 | — | 公式の一覧に無い |
使えるかどうかは、1回のGETで分かる
authentication キーを見る
インストールも設定も要りません。REST APIのルートURLを叩いて、authentication キーを見るだけです。
公式の統合ガイドがこの方法を案内しています。「空なら Application Passwords は使えません」。
curl -s https://example.com/wp-json/ | jq .authentication
使える状態なら、こう返ってきます。
{
"application-passwords": {
"endpoints": {
"authorization": "https://example.com/wp-admin/authorize-application.php"
}
}
}
{}(空のオブジェクト)が返ったら使えません。
HTTPSでないか、プラグインで無効化されているか、のどちらかです。
運用中の11サイトで叩いてみた
手元で運用しているWordPressサイト11件に対して、実際に /wp-json/ をGETしました。
どれも公開エンドポイントへの読み取りだけで、認証も書き込みもしていません。
| 結果 | 件数 | 内容 |
|---|---|---|
HTTP 200・application-passwords あり | 9 | そのまま使える |
| HTTP 404・HTMLが返る | 1 | 静的書き出しのサイト。WordPress本体は別ドメインにある |
| 接続できず(タイムアウト) | 1 | サイト全体に到達できず。APIの問題ではない |
9件はすべて、何も設定していないのに使える状態でした。 テーマもプラグイン構成もバラバラで、共通していたのはHTTPSで動いていることだけです。 「使えるようにする作業」はほぼ不要で、確認するだけで済みます。
使えなかった2件の理由
残り2件は、原因がまったく別でした。
| 症状 | 原因 | 対処 |
|---|---|---|
/wp-json/ が404でHTMLを返す | そのドメインは静的HTMLの書き出しで、WordPressが動いていない | 本体が動いているドメインを叩く |
| 接続自体ができない | サイトが落ちているか、アクセスを弾かれている | トップページも叩いて切り分ける |
1件目は、静的書き出しのサイトでよく踏みます。
見た目はWordPressのサイトなのに、公開されているのは書き出されたHTMLだけなので、
/wp-json/ は単なる存在しないパスになります。
このとき返ってくるのは404のHTMLです。それをJSONとして解釈しようとすると、こうなります。
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
手元で実際に出した出力です。「APIが壊れている」のではなく、そこにAPIが無いだけです。 このエラーの読み方は Unexpected token '<' ... is not valid JSON の原因と直し方 に書きました。
2件目は、トップページも叩いて切り分けました。
トップページも応答しないなら、APIの話ではありません。
/wp-json/ だけが失敗するのか、サイト全体が失敗するのかを先に分けてください。
Application Passwords を使う
発行する
wp-admin → ユーザー → 自分のプロフィールを開き、下のほうにある 「アプリケーションパスワード」で名前を付けて発行します。 表示されるのはその1回だけなので、その場で控えます。
| 項目 | 内容 |
|---|---|
| 長さ | 24文字 |
| 使われる文字 | 大文字・小文字・数字。記号は含まれない |
| 表示形式 | abcd EFGH 1234 ijkl MNOP 6789 のように区切って表示される |
| スペースの扱い | スペースを抜いて使ってもよい |
| 管理できる人 | ユーザーを編集できる権限があれば誰でも(細分化は今後の予定) |
| 前提 | SSL/HTTPSで配信されているサイト |
表示のスペースは読みやすさのためのもので、入れても抜いても通ります。 ここで迷って「コピーミスかもしれない」と何度も発行し直す必要はありません。
HTTPSが前提なのは仕様です。公式はこう書いています。 「SSLがなければ、ネットワーク上の攻撃者にアプリケーションパスワードを見られる可能性があります」。 Basic認証は、中身をほぼそのまま送っているだけだからです。
curlで叩く
公式に載っている形です。ユーザー名とアプリケーションパスワードを --user に渡します。
# 公式ドキュメントの例
curl --user "USERNAME:PASSWORD" https://HOSTNAME/wp-json/wp/v2/users?context=edit
# 実際に使う形(スペースは抜いてよい)
curl --user "subburi:abcdEFGH1234ijklMNOP6789" \
https://example.com/wp-json/wp/v2/posts?context=edit&per_page=1
投稿するときは、ヘッダとJSONを付けてPOSTします。
curl -X POST \
--user "subburi:abcdEFGH1234ijklMNOP6789" \
-H "Content-Type: application/json" \
-d '{"title":"テスト","content":"本文","status":"draft"}' \
https://example.com/wp-json/wp/v2/posts
"status":"draft" を付けておくのが安全です。
省略すると既定値で作られます。最初の1回は下書きで試して、応答の id と link を確認してください。
ブラウザの中から呼ぶときは nonce
管理画面のJSやテーマから同じサイトのAPIを呼ぶ場合は、話が変わります。 ログイン中のCookieがすでにあるので、認証そのものは済んでいます。 足りないのはCSRF対策のnonceです。
公式の説明で、一番効くのはこの一文です。
「nonceが渡されなかった場合、APIは現在のユーザーを 0 に設定し、
WordPressにログインしていてもそのリクエストを未認証リクエストとして扱います」
— Authentication(REST API Handbook・要約)
ログインしているのに401が返る、という症状の多くはこれです。 エラーは「権限がない」ように見えますが、WordPress側から見れば未ログインの人が叩いています。
渡し方は、PHP側でnonceを出して、JS側でヘッダに載せます。
<?php
// PHP側:スクリプトにnonceを渡す(公式の例)
wp_localize_script( 'wp-api', 'wpApiSettings', array(
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' )
) );
// JS側:X-WP-Nonce ヘッダに載せる
fetch( wpApiSettings.root + 'wp/v2/posts/1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce,
},
credentials: 'same-origin',
body: JSON.stringify( { title: 'Hello Moon' } ),
} );
| 方法 | 書き方 | 評価 |
|---|---|---|
| ヘッダ | X-WP-Nonce | 最も確実。これを使う |
| クエリ / POSTデータ | _wpnonce | 動くが、DELETEでは不安定 |
公式が「ヘッダが最も確実」と書いているのは DELETE のためです。
PHPは DELETE のリクエストボディをスーパーグローバルに展開しません。
クエリやボディに入れる方式だと、DELETE のときだけ落ちます。
nonceのアクション名は wp_rest 固定です。
自分のエンドポイントの中でnonceを検証し直す必要はありません。
WordPressが rest_cookie_check_errors() で自動的にやってくれます。
ブラウザに配るJSにアプリケーションパスワードを書かないでください。 ソースを見れば誰でも読めます。アプリケーションパスワードは サーバー側のコードからだけ使うものです。 ブラウザから呼ぶなら、使うのはCookieとnonceです。
401が返るときの切り分け
まずエラーコードを読む
WordPressのREST APIは、失敗の理由をJSONの code で返します。
HTTPステータスだけ見ていると区別がつきません。本文を読んでください。
手元のサイトで、認証なしのまま各エンドポイントを叩いた結果です。
| 叩いたもの | HTTP | code | 読み方 |
|---|---|---|---|
/wp/v2/posts | 200 | — | 公開エンドポイント。認証なしで読める |
/wp/v2/settings | 401 | rest_forbidden | 保護されている。認証が要る |
/wp/v2/users?context=edit | 404 | rest_no_route | そのルート自体が無い |
3行目が要注意です。公式ドキュメントに載っているURLなのに 404 が返りました。
認証の問題ではなく、そのサイトでユーザー一覧のエンドポイントが塞がれているということです。
セキュリティプラグインや functions.php での対策としてよく行われます。
rest_no_route が返ったら、認証を疑う前にエンドポイントの存在を疑ってください。
いくら正しいパスワードを渡しても404のままです。
Authorizationヘッダが落ちている
パスワードは合っているのに401が返り続ける場合、最有力はこれです。 公式のFAQに項目があります。
「認証ヘッダを送っているのにリクエストが受け付けられず、CGI環境を使っている場合、
Webサーバーがヘッダを取り除いている可能性があります。
以下の設定を追加して解決を試みてください」
— Frequently Asked Questions(REST API Handbook・要約)
公式が載せている設定です。
# Apache(.htaccess または設定ファイル)
<IfModule mod_setenvif>
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
</IfModule>
# Nginx(fastcgi の設定セクション)
fastcgi_pass_header Authorization;
| 構成 | 対処 |
|---|---|
| PHPがCGI / FastCGIで動いている(mod_phpでない) | 上のApache設定を # BEGIN WordPress より前に書く |
| Nginx + PHP-FPM | fastcgi_pass_header Authorization; |
| Nginxが前段、Apacheが後段 | 両方に設定が要る場合がある |
| 共有レンタルサーバーで設定ファイルを触れない | .htaccessで試す。だめならREST APIを諦める判断も要る |
この症状の見分け方は、エラーコードです。
ヘッダが届いていなければ、WordPressから見れば誰も名乗っていないので
rest_not_logged_in 系になります。
パスワードが違うなら incorrect_password が返るので、そこで区別できます。
それでも通らないとき
| 確認すること | 症状 |
|---|---|
| プロフィール画面にアプリケーションパスワードの項目があるか | 無ければ、セキュリティプラグインが隠している |
/wp-json/ の authentication が空でないか | 空なら機能が無効(確認方法) |
| HTTPSで叩いているか | httpだと既定で使えない |
| ログインパスワードを使っていないか | アプリケーションパスワードは別物 |
| そのユーザーに権限があるか | 投稿するなら投稿できる権限が要る |
| アプリケーションパスワードをプラグインで入れていないか | コアの機能と競合することがある |
1行目を先に見てください。プロフィール画面に項目が出ていないなら、そこで話が終わります。 curlをいくら書き直しても通りません。
公開エンドポイントは誰でも叩ける
認証を整える前に、知っておくべきことがあります。 WordPressのREST APIは、リクエストのOriginヘッダを検証しません。
公式FAQの説明はこうです。
CORSの制限を強めると一部の認証方法が使えなくなるため、
WordPressのREST APIはCORSではなくnonceをCSRF対策として使っている。
Originヘッダを検証しないので、公開REST APIエンドポイントはどのサイトからでもアクセスできる。
— Frequently Asked Questions(REST API Handbook・要約)
これは設計上の意図であって、不具合ではありません。 ただし、公開したくないものが公開エンドポイントから読めていないかは、確認する価値があります。
| エンドポイント | 見えるもの | 考えること |
|---|---|---|
/wp/v2/users | ユーザー名・スラッグ | ログインIDの推測材料になる。塞いでいるサイトが多い |
/wp/v2/posts | 公開済みの投稿 | 公開情報なので基本は問題ない |
/wp/v2/pages | 公開済みの固定ページ | 同上 |
/wp/v2/media | メディアライブラリの項目 | 記事に貼っていない画像も見える |
/wp-json/ | 有効なプラグインの名前空間 | 使っているプラグインが推測できる |
最後の行は、今回11サイトを叩いたときに実感しました。
/wp-json/ の namespaces を見るだけで、
SEOプラグイン、問い合わせフォーム、リダイレクト管理など、入れているものがかなり分かります。
塞ぐかどうかは別として、見えているという事実は知っておいたほうがいいです。
AIに任せるときの注意
| 頼むこと | 向くか | 理由 |
|---|---|---|
| 「Application Passwordsで投稿するスクリプトを書いて」 | 向く | 定型。Basic認証とJSONを組むだけ |
curlの応答(code 付き)を貼って「原因は?」 | 向く | エラーコードから切り分けられる |
/wp-json/ の出力を貼って「使える?」 | 向く | authentication を見るだけ |
| 「WordPress REST APIの認証方法を教えて」 | 向かない | OAuth 2プラグインの話が返る。5.6以前の情報が大量にある |
| 「401が出ます、直して」(情報なし) | 向かない | 原因が5つ以上ある。切り分けを先にやる |
| サーバー設定の修正を任せる | 向かない | .htaccessの書き換えは壊すと全ページ落ちる。必ず控えを取る |
判定は簡単です。「プラグインをインストールして Client ID を取得して」と言い出したら、情報が古いと思ってください。 2026年の答えは、プロフィール画面でパスワードを1つ発行するだけです。
よくある質問
OAuth 2 のプラグインは必要ですか
ほぼ不要です。公式が認証プラグインとして挙げているのは OAuth 1.0a と JWT で、OAuth 2 ではありません(表3)。 外部アプリから叩くだけなら Application Passwords で足ります。
アプリケーションパスワードの項目が見当たりません
HTTPSでないか、セキュリティプラグインが隠しています。
/wp-json/ の authentication が空かどうかで確認できます(詳しく)。
パスワードのスペースは入力するのですか
どちらでも通ります。公式が「スペースなしでも使える」と明記しています(表6)。 表示の区切りは読みやすさのためのものです。
ログインしているのに401が返ります
ブラウザ内から呼んでいるなら、nonceが渡っていません。
公式は「nonceが無ければ現在のユーザーを 0 に設定し、ログインしていても未認証リクエストとして扱う」と書いています(詳しく)。
X-WP-Nonce ヘッダを付けてください。
パスワードは合っているのに401が返ります
Authorizationヘッダがサーバーで落とされている可能性が高いです(詳しく)。
CGI/FastCGI環境でよく起きます。公式FAQの設定を入れてください。
応答の code が incorrect_password ならパスワードの問題、
未認証扱いの系ならヘッダの問題です。
404と rest_no_route が返ります
認証の前に、そのエンドポイントが存在しません。 URLかメソッドの間違いか、プラグイン等でそのルートが塞がれています(表8)。 実際、公式ドキュメントに載っているユーザー一覧のURLでも404になるサイトがあります。
/wp-json/ を叩いたらHTMLが返ります
そのドメインでWordPressが動いていません。静的に書き出したサイトでよくあります(表5)。
それをJSONとして読むと Unexpected token '<' になります。
詳しくは こちら。
DELETEだけ失敗します
nonceをクエリやボディで渡していませんか。
PHPは DELETE のリクエストボディをスーパーグローバルに展開しません。
X-WP-Nonce ヘッダで渡してください(表7)。
ブラウザのJSにアプリケーションパスワードを書いてもいいですか
いけません。ソースを見れば誰でも読めます。 サーバー側のコードからだけ使ってください。ブラウザから呼ぶならCookieとnonceです。
まとめ
- 外部から叩くなら Application Passwords、ブラウザの中からなら Cookie+nonce。この2つでほぼ終わる。
- Application Passwords は WordPress 5.6からコア同梱。プラグインもClient IDも有料ライセンスも要らない。
- OAuth 2 は公式の認証手段の一覧に無い。公式が挙げているのは OAuth 1.0a と JWT。
- 使えるかどうかは
/wp-json/のauthenticationキーを見れば1回のGETで分かる。空なら使えない。 - 運用中の11サイトで実測したら、9件は何もせず使える状態だった。共通点はHTTPSで動いていることだけ。
- パスワードは24文字・記号なし。表示のスペースは抜いて使ってよい。
- ログイン中なのに401なら、nonceが渡っていない。WordPressは未認証リクエストとして扱う。
- パスワードが合っているのに401なら、Authorizationヘッダがサーバーで落とされている。CGI/FastCGIで起きる。
rest_no_routeは認証の問題ではない。そのエンドポイントが無いか、塞がれている。- 公開エンドポイントはOriginを検証しないのでどこからでも叩ける。何が見えているかは確認しておく。
/wp-json/ がHTMLを返したときのエラーは
Unexpected token '<' ... is not valid JSON の原因と直し方、
ブラウザから叩くJSの話は Vue 3 でセレクトボックスからページ遷移する と
localStorage の使い方、
SPAでの計測は GA4でSPAのページビューが計測されない5つの原因 に書きました。
11サイトの実測は2026年10月25日に、公開されている /wp-json/ へのGETだけで行ったものです
(認証も書き込みもしていません)。サイト名は伏せています。
仕様とプラグイン構成は変わります。自分のサイトで同じGETを1回叩いて確認してください。
.htaccess を書き換えるときは、必ず控えを取ってからにしてください。書き損じると全ページが落ちます。