WordPressで特定ページだけにBasic認証をかける方法。functions.phpで実装する

WordPress(ワードプレス)のイメージ

公開前のページや関係者にだけ見せたいページを、手軽にアクセス制限したい。そんなとき真っ先に思い浮かぶのが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環境での挙動、パスワードの管理方法まで押さえたうえで、公開前の確認用ページなど「軽く隠したい」用途に使うのがちょうどいいと感じています。

参考:カテゴリーページや固定ページ等でBasic認証を利用する(KOTORI Blog)