公開前のページや関係者にだけ見せたいページを、手軽にアクセス制限したい。そんなとき真っ先に思い浮かぶのがBasic認証です。ディレクトリ単位なら.htaccessに数行書くだけで済むのですが、WordPressの固定ページや投稿には「ディレクトリ」という概念がありません。URLは書き換えられているだけで、実体はすべて index.php が処理しています。だから.htaccessでは「このページだけ」を狙い撃ちできないわけです。
そこで私が使っているのが、functions.php にPHPで認証処理を書き、条件分岐で特定のページにだけBasic認証をかけるやり方です。この記事ではその実装を、現行のWordPressで動く形にまとめました。
仕組み:PHPのBasic認証をWordPressのフックで動かす
Basic認証そのものはPHPの標準機能で実現できます。ブラウザに WWW-Authenticate ヘッダーと 401 を返すと認証ダイアログが出て、ユーザーが入力した値が $_SERVER['PHP_AUTH_USER'] と $_SERVER['PHP_AUTH_PW'] に入ってくる、という流れです(PHPマニュアル:HTTP 認証)。
ポイントは、この処理を出力が始まる前に走らせること。ヘッダーは本文より先に送る必要があるからです。古い解説では header.php に直接書く方法が定番でしたが、テーマによっては出力タイミングがずれて動かないことがあります。そこで私は、テンプレートの読み込み直前に発火する template_redirect フックを使います。ここはヘッダー送信やリダイレクトを行うのに適したタイミングだと、WordPress公式ドキュメントでも案内されています(template_redirect | WordPress Developer Resources)。
固定ページにBasic認証をかける
1. 認証チェックの関数を用意する
まず、ユーザー名とパスワードを照合する関数を functions.php に追加します。合致すればそのまま通し、そうでなければ認証ダイアログを出す、という素直な作りです。
<?php
function wn_basic_auth( $auth_list, $realm = 'Restricted Area' ) {
$user = isset( $_SERVER['PHP_AUTH_USER'] ) ? $_SERVER['PHP_AUTH_USER'] : '';
$pw = isset( $_SERVER['PHP_AUTH_PW'] ) ? $_SERVER['PHP_AUTH_PW'] : '';
// 登録済みユーザーで、かつパスワードが一致すれば通過
if ( $user !== '' && isset( $auth_list[ $user ] ) && hash_equals( $auth_list[ $user ], $pw ) ) {
return;
}
// 未認証・失敗時はダイアログを表示
header( 'WWW-Authenticate: Basic realm="' . $realm . '"' );
header( 'HTTP/1.1 401 Unauthorized' );
exit( '認証に失敗しました。' );
}
パスワードの照合には == ではなく hash_equals() を使っています。単純な文字列比較よりタイミング攻撃に強く、PHPマニュアルでも認証用途で推奨されている関数です。realm は認証ダイアログに表示される領域名なので、必要なら分かりやすい名前に変えてください。
2. 対象ページを指定して認証を呼び出す
次に、実際にどのページで認証をかけるかを template_redirect フックで指定します。同じく functions.php に書きます。
add_action( 'template_redirect', function () {
// 認証をかけたい固定ページのID(複数可)
if ( is_page( array( 1, 2, 3 ) ) ) {
$users = array(
'staff' => 'CHANGE_ME_password', // ユーザー名 => パスワード
);
wn_basic_auth( $users );
}
} );
is_page() に固定ページのIDを配列で渡せば、そのページだけに認証がかかります。IDの代わりにスラッグやタイトルでも指定できます。ユーザー名・パスワードはサンプルなので、必ず自分の値に置き換えてください。
複数ユーザーを許可したい場合
配列に行を足すだけで、複数の組み合わせを受け付けられます。
$users = array(
'staff' => 'password_a',
'client' => 'password_b',
);
カテゴリーページや投稿にかける
条件分岐を差し替えれば、対象を柔軟に変えられます。特定カテゴリーのアーカイブと、そのカテゴリーに属する個別投稿の両方をまとめて制限したいなら、is_category() と in_category() を併用します。
add_action( 'template_redirect', function () {
// カテゴリーID 5 のアーカイブと、その中の個別投稿を制限
if ( is_category( 5 ) || in_category( 5 ) ) {
$users = array( 'staff' => 'CHANGE_ME_password' );
wn_basic_auth( $users );
}
} );
カスタム投稿タイプにかける
特定のカスタム投稿タイプだけを制限したいときは is_singular() が便利です。個別ページとアーカイブをまとめて対象にするなら、次のように書けます。
add_action( 'template_redirect', function () {
// 投稿タイプ 'event' の個別・アーカイブを制限
if ( is_singular( 'event' ) || is_post_type_archive( 'event' ) ) {
$users = array( 'staff' => 'CHANGE_ME_password' );
wn_basic_auth( $users );
}
} );
実装するときの注意点
実際に運用してみると、いくつか引っかかりやすいポイントがあります。私が特に気をつけているのは次の3つです。
CGI/FastCGI環境では認証情報が取れないことがある
サーバーのPHPがCGIやFastCGIとして動いていると、$_SERVER['PHP_AUTH_USER'] にそもそも値が渡ってこず、何度入力しても認証を通過できないことがあります。CGIの仕組み上、認証ヘッダーがPHPまで届かないためです(これもPHPマニュアルに明記されています)。
回避策として、.htaccess に次のような一行を足し、Authorizationヘッダーを環境変数として渡してやる方法があります。
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
そのうえで、関数の冒頭に「HTTP_AUTHORIZATION があれば自前でデコードして PHP_AUTH_USER に入れ直す」処理を足しておくと確実です。Apache 2.4.13以降なら CGIPassAuth On の指定でも対応できます。
必ずSSL(https)を併用する
Basic認証はユーザー名・パスワードを暗号化せずに送ります。https化していないページで使うと、通信を覗かれれば認証情報がそのまま漏れます。PHPマニュアルでも「本格的なものにBasic認証を使うのは避けるべき」とされているとおり、あくまで簡易的な閲覧制限と割り切り、SSLの併用を前提にしてください。
パスワードをコードに直書きするリスク
上のサンプルはパスワードを functions.php に直接書いています。手軽な反面、テーマファイルを閲覧できる人には丸見えになりますし、テーマ更新やGit管理で流出する危険もあります。長く運用するなら、wp-config.php の定数や環境変数から読み込む、あるいは認証情報を管理画面から扱えるプラグインを使う、といった形にしておくと安心です。
SEOへの影響も頭に入れておく
認証をかけたページは検索エンジンのクローラーも入れなくなるため、インデックスされません。公開して集客したいページには使わない、という当たり前の切り分けは忘れないようにしています。
まとめ
.htaccessでは難しい「WordPressの特定ページだけ」の制限も、functions.php に認証関数を用意し、template_redirect で対象を条件分岐すれば実現できます。固定ページ・カテゴリー・カスタム投稿タイプと、条件を差し替えるだけで応用が効くのが便利なところです。
ただしBasic認証はあくまで簡易的な仕組みです。SSLの併用、CGI環境での挙動、パスワードの管理方法まで押さえたうえで、公開前の確認用ページなど「軽く隠したい」用途に使うのがちょうどいいと感じています。