2026年8月3日
2026年8月3日
PHPのSESSIONとCookieの管理方法:WordPressでsession_startを使わずに訪問者の状態を保持する
WordPress のテーマやプラグインで「訪問者ごとの状態」を持ちたいときは、PHP の $_SESSION ではなく、ランダムなIDだけを入れた Cookie と、サーバー側のトランジェントやユーザーメタを組み合わせる方法が扱いやすい選択です。WordPress 本体のログインも PHP セッションではなく、Cookie とユーザーごとのセッショントークンで動いています。この記事では、WordPress がログイン状態をどう管理しているか、session_start() が問題を起こす理由、セッションを使わずに状態を保持する実装、そしてキャッシュと個人データの注意点までを説明します。PHP 単体でのセッション設定(session.use_strict_mode など)の基本は別記事で扱っているので、ここでは WordPress 上での設計に絞ります。
WordPressのログインはPHPセッションを使っていない
ログインに成功すると、WordPress はブラウザにいくつかの Cookie を発行します。名前は wordpress_logged_in_ に続けてサイト固有のハッシュ(COOKIEHASH 定数)を付けた形で、管理画面用には wordpress_ または HTTPS の場合 wordpress_sec_ で始まる Cookie が別に発行されます。Cookie の中身は、ユーザー名、有効期限、セッショントークン、それらから作った署名です。署名には wp-config.php の認証用キーとソルトが使われるため、利用者が値を書き換えると検証に失敗します。
サーバー側では、ログインごとに作られたセッショントークンのハッシュが、ユーザーメタ(session_tokens)に保存されます。この仕組みは WP_Session_Tokens クラス(標準の実装は WP_User_Meta_Session_Tokens)が担当しています。プロフィール画面の「他のすべての場所からログアウト」のような操作は、このトークンを消すことで実現しています。コードから同じことをするには、次の関数が使えます。
// 今のログイン以外のセッションをすべて無効にする(ログイン中のユーザー本人に対して)
wp_destroy_other_sessions();
// 特定ユーザーのセッションをすべて無効にする(管理者用の処理など)
$manager = WP_Session_Tokens::get_instance( $user_id );
$manager->destroy_all();
ログイン状態の保持期間は、通常2日、「ログイン状態を保存する」にチェックした場合は14日が既定です。変更したい場合は auth_cookie_expiration フィルターを使います。期間を長くすると、端末の紛失や共用 PC で他人に使われる危険も長くなるため、管理者権限を持つユーザーは短めにする、といった調整が現実的です。
// mu-plugin などに置く例:管理者だけ「保存する」でも3日にする
add_filter( 'auth_cookie_expiration', function ( $length, $user_id, $remember ) {
if ( $remember && user_can( $user_id, 'manage_options' ) ) {
return 3 * DAY_IN_SECONDS;
}
return $length;
}, 10, 3 );
session_start()を呼ぶと起きやすい問題
プラグインやテーマが session_start() を呼ぶと、WordPress の仕組みと合わない点がいくつか出てきます。よく見かける順に並べます。
- ページキャッシュとの衝突: セッションを開始すると
PHPSESSIDの Cookie が発行されます。キャッシュプラグインやサーバーのキャッシュは、Cookie を持つリクエストをキャッシュ対象から外すことが多く、ほぼ全員がキャッシュなしで表示される状態になります。逆に設定によっては、ある訪問者向けの表示がキャッシュされ、別の人に見えてしまうこともあります。 - リクエストの順番待ち: ファイル保存のセッションは、処理が終わるまでセッションファイルをロックします。同じ訪問者からの Ajax や REST API のリクエストが同時に来ると、1件ずつしか処理されず、管理画面やブロックエディターが遅くなります。
- サイトヘルスの警告: セッションが開いたまま HTTP リクエストを行うと、REST API やループバックリクエストの確認が失敗し、サイトヘルスに PHP セッションに関する警告が表示されることがあります。
- ヘッダー送信後のエラー:
session_start()は Cookie を送るため、画面への出力が始まったあとに呼ぶと「headers already sent」の警告になります。
どうしても PHP セッションが必要なライブラリを使う場合は、必要な画面だけで開始し、読み取りだけなら session_start( array( 'read_and_close' => true ) ) を使う、書き込んだらすぐ session_write_close() を呼ぶ、といった形でロックの時間を短くします。
セッションを使わずに訪問者の状態を保持する
ログインしていない訪問者の「最近見た商品」や「入力途中のフォーム」などを保持したい場合は、次の分担にします。
- Cookie: 推測できないランダムなIDだけを入れる。中身のデータは入れない。
- サーバー側: そのIDをキーにして、トランジェントや独自テーブルにデータを保存する。
Cookie に直接データを入れると、利用者が自由に書き換えられます。IDだけにしておけば、書き換えられても他人のデータに当たる確率は無視できるほど小さく、形式の検査で不正な値をはじけます。
<?php
/**
* 訪問者IDの Cookie を取得し、なければ発行する。
* template_redirect など、出力の前に呼ぶこと。
*/
function myplugin_get_visitor_id() {
$name = 'myplugin_vid';
if ( isset( $_COOKIE[ $name ] ) ) {
$value = sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ) );
if ( preg_match( '/^[a-f0-9]{32}$/', $value ) ) {
return $value;
}
}
if ( headers_sent() ) {
return '';
}
$id = bin2hex( random_bytes( 16 ) );
setcookie( $name, $id, array(
'expires' => time() + DAY_IN_SECONDS,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN ? COOKIE_DOMAIN : '',
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
) );
$_COOKIE[ $name ] = $id;
return $id;
}
/** 訪問者ごとのデータを読み書きする */
function myplugin_get_visitor_data() {
$id = myplugin_get_visitor_id();
if ( '' === $id ) {
return array();
}
$data = get_transient( 'myplugin_v_' . $id );
return is_array( $data ) ? $data : array();
}
function myplugin_set_visitor_data( array $data ) {
$id = myplugin_get_visitor_id();
if ( '' !== $id ) {
set_transient( 'myplugin_v_' . $id, $data, DAY_IN_SECONDS );
}
}
ポイントを補足します。
random_bytes()は暗号論的に安全な乱数を返します。rand()やuniqid()は推測されやすいので ID には使いません。- 配列で属性を渡す
setcookie()の書き方は PHP 7.3 以降で使えます。httponlyを付けると JavaScript から読めなくなり、secureは HTTPS のときだけ送る指定です。SameSite=Laxは、ほかのサイトから送られる POST などに Cookie が付くのを防ぎます。 COOKIEPATHとCOOKIE_DOMAINを使うと、WordPress のログイン Cookie と同じ範囲になります。サブディレクトリにインストールしたサイトで、ほかのアプリと Cookie が混ざるのを避けられます。- トランジェントは、Redis などのオブジェクトキャッシュを使っている環境ではメモリ上に置かれ、期限前に消えることがあります。消えても困らないデータ向けです。注文情報のように失えないデータは、独自テーブルに保存します。
ログイン中のユーザーに結びつくデータなら、Cookie は不要です。get_current_user_id() で ID を取り、update_user_meta() と get_user_meta() で保存すれば、端末を変えても同じデータを使えます。WooCommerce のように独自のセッション機構(専用テーブルに保存する方式)を持つプラグインもあるので、その上で開発する場合は、そのプラグインの仕組みに従うほうが安全です。
状態を変える処理にはnonceと権限チェックを付ける
Cookie で訪問者を識別できても、それだけでは「本人がその操作を意図した」ことは分かりません。フォームの送信や Ajax で状態を変える処理には、nonce を付けて検証します。
// フォーム側
wp_nonce_field( 'myplugin_save_list', 'myplugin_nonce' );
// 受け取る側(admin-post.php や Ajax のハンドラー)
if ( ! isset( $_POST['myplugin_nonce'] )
|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myplugin_nonce'] ) ), 'myplugin_save_list' ) ) {
wp_die( esc_html__( '不正なリクエストです。', 'myplugin' ), 403 );
}
ログインユーザーのデータを変える処理では、current_user_can() で権限も確認します。REST API を JavaScript から呼ぶ場合は、wp_create_nonce( 'wp_rest' ) で作った値を X-WP-Nonce ヘッダーで送らないと、ログイン Cookie があってもログインしていない扱いになります。これは CSRF を防ぐための仕様です。
保存した値を画面に出すときは、esc_html() や esc_attr() でエスケープします。Cookie もトランジェントも、利用者の入力から作られた値であることに変わりはありません。
ページキャッシュとの付き合い方
訪問者ごとに内容が変わる部分は、ページキャッシュと相性がよくありません。対処は次のどれかです。
- その部分だけ後から読み込む: ページ本体はキャッシュし、訪問者ごとの部分は REST API や Ajax で取得して表示します。サイト全体の速度を保ちやすい方法です。
- 特定のページだけキャッシュから外す: カートや入力フォームなど、ページ全体が訪問者ごとに変わる場合は、キャッシュプラグインの除外設定に URL を追加します。設定の名前はプラグインによって異なります。
- Cookie でキャッシュを分ける: 多くのキャッシュプラグインやサーバーは、特定の Cookie があるときにキャッシュを使わない設定を持っています。独自 Cookie の名前を登録する方法ですが、Cookie を持つ人が増えるほどキャッシュの効果は下がります。
設定後は、ログアウトした状態で別のブラウザを2つ使い、片方の状態がもう片方に見えないかを確認してください。レスポンスヘッダーはブラウザの開発者ツールのネットワークタブ、または curl -I https://example.com/ で、Set-Cookie やキャッシュ関連のヘッダーを見て確認できます。
個人データとプライバシーポリシー
訪問者を識別する Cookie は、利用目的によっては個人に関する情報の扱いになります。どの Cookie を何の目的で使い、どれだけの期間保存するかをプライバシーポリシーに書いておきます。EU の利用者がいるサイトや、外部サービスへの情報送信がある場合は、同意の取得や公表が必要になる場合があります。法令や各ブラウザの Cookie の扱いは変わり続けているため(2026年10月現在)、最新の情報は専門家や公式の資料で確認してください。
期限が来たトランジェントは、取得されたときやデータベースの定期処理で消されますが、独自テーブルに保存する場合は、古い行を削除する WP-Cron の処理も用意します。保存期間を決めずにため続けないことが、個人データの扱いとしても容量の面でも大切です。
本番サイトに組み込む前に、ステージング環境でキャッシュの有無を変えながら動作を確かめ、データベースのバックアップを取ってから反映してください。
まとめ
- WordPress のログインは PHP セッションではなく、署名付きの Cookie とユーザーメタのセッショントークンで管理されている。
session_start()はページキャッシュとの衝突、リクエストのロック、サイトヘルスの警告の原因になりやすい。- 訪問者の状態は「ランダムIDだけの Cookie+トランジェントや独自テーブル」、ログインユーザーならユーザーメタに保存する。
- Cookie には
Secure・HttpOnly・SameSiteを付け、状態を変える処理には nonce と権限チェックを入れる。 - 訪問者ごとに変わる部分はキャッシュから分け、Cookie の目的と保存期間をプライバシーポリシーに書く。