2026年5月22日
2026年5月22日
PHPのセッションとCookieの管理方法|安全な設定・ログイン時の扱い・WordPressでの注意点
PHPでログイン状態や一時的な入力内容を保持するには、サーバー側に値を置く「セッション($_SESSION)」と、ブラウザ側に値を置く「Cookie(setcookie())」を使い分けます。安全に使うための要点は、Cookieに Secure・HttpOnly・SameSite を付けること、ログイン直後にセッションIDを作り直すこと、Cookieに秘密や権限の情報を入れないことの3つです。この記事では、両者の違いから、具体的な設定コード、ログイン・ログアウト時の処理、よくあるトラブル、WordPressでPHPセッションを使うときの注意点までを説明します。
セッションとCookieの違い
Cookieは、サーバーがブラウザに「この値を保存して、次から送ってきて」と頼む仕組みです。値はブラウザに保存されるため、利用者が自由に書き換えられます。
セッションは、データ本体をサーバー側(既定ではファイル)に保存し、ブラウザにはそのデータを指す「セッションID」だけをCookieで渡す仕組みです。利用者が見られるのはIDだけなので、ログイン中のユーザーIDのような値はセッションに置きます。
| セッション | Cookie | |
|---|---|---|
| 値の保存場所 | サーバー | ブラウザ |
| 利用者による改ざん | 中身は直接触れない | 自由に書き換えられる |
| 向いている用途 | ログイン状態、フォームの途中経過 | 表示設定、言語の選択など |
| 期限 | 一定時間アクセスがないと破棄 | expires で指定 |
この性質の違いから、「Cookieの値は信用しない」が出発点になります。Cookieに role=admin のような値を入れて権限を判断するのは避けてください。
Cookieを安全に設定する
PHP 7.3以降は、setcookie() にオプションを配列で渡せます。属性の付け忘れが減るので、この書き方を使います。
<?php
setcookie('display_mode', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30, // 30日
'path' => '/',
'domain' => '', // 空にすると現在のホストのみ
'secure' => true, // HTTPS のときだけ送る
'httponly' => true, // JavaScript から読めなくする
'samesite' => 'Lax', // 他サイトからの遷移時の送信を制限
]);
各属性の意味は次のとおりです。
- **
secure** … HTTPS接続でのみ送信します。サイト全体がHTTPSなら常にtrueにします。 - **
httponly** …document.cookieから読めなくなり、XSSが起きたときにCookieを盗まれる被害を抑えられます。JavaScriptで読む必要がある値だけ外します。 - **
samesite** … 他サイトからのリクエストにCookieを付けるかどうかです。Laxが一般的な選択で、外部サイトからのフォーム送信(POST)には付きません。Noneにする場合はsecureが必須です。 - **
domain** … 指定するとサブドメインにも送られます。不要なら空にして範囲を狭くします。
読み取りは $_COOKIE['display_mode'] で行いますが、値は利用者が自由に変えられるため、許可する値の一覧と照合してから使います。
$allowed = ['light', 'dark'];
$mode = in_array($_COOKIE['display_mode'] ?? '', $allowed, true) ? $_COOKIE['display_mode'] : 'light';
Cookieを削除するときは、設定したときと同じ path と domain で、過去の期限を指定して上書きします。属性が違うと別のCookieとして扱われ、消えません。
Cookieの名前を __Host- で始めると、ブラウザは「Secure 付き・path=/・domain 指定なし」の場合だけ保存を受け付けます。重要なCookieの範囲を誤って広げないための仕組みとして使えます。
セッションの安全な設定
セッションIDを運ぶCookieにも同じ属性を付けます。php.iniで設定するか、session_start() の前にコードで指定します。
; php.ini(または .user.ini / PHP-FPM のプール設定)
session.use_strict_mode = 1
session.use_only_cookies = 1
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
session.gc_maxlifetime = 1800
<?php
session_set_cookie_params([
'lifetime' => 0, // ブラウザを閉じるまで
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
session.use_strict_mode を有効にすると、サーバーが発行していないセッションIDを受け付けなくなります。攻撃者が用意したIDを使わせる「セッション固定攻撃」への対策の1つです。
session.gc_maxlifetime は、最後のアクセスからセッションデータを破棄してよいとみなすまでの秒数です。ただし、破棄の処理(ガベージコレクション)がいつ動くかは設定やOSのパッケージによって異なり、Debian系のように定期実行のジョブで掃除する構成もあります。厳密な有効期限が必要なら、最終アクセス時刻をセッションに保存し、アプリ側で判定します。
ログイン・ログアウト時の処理
ログインに成功したら、session_regenerate_id(true) でセッションIDを新しくします。ログイン前のIDを使い続けると、そのIDを知っている第三者がログイン後の状態に入れてしまいます。
<?php
session_start();
// パスワードは password_hash() で保存したハッシュと照合する
if ($user && password_verify($password, $user['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = (int) $user['id'];
$_SESSION['last_seen'] = time();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
パスワードを平文でセッションやログに入れないこと、ログイン試行の回数を制限すること(同じアカウント・IPからの連続失敗で待ち時間を入れるなど)もあわせて実装します。フォームからの送信には、上の csrf_token のようなトークンを埋め込み、受信時に hash_equals() で照合します。
ログアウトでは、セッションの中身を消し、Cookieも削除してからセッションを破棄します。
<?php
session_start();
$_SESSION = [];
$params = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 42000,
'path' => $params['path'],
'domain' => $params['domain'],
'secure' => $params['secure'],
'httponly' => $params['httponly'],
'samesite' => $params['samesite'],
]);
session_destroy();
よくあるトラブルと確認方法
「Cannot modify header information - headers already sent」と出るCookieとセッションの開始はHTTPヘッダーで送られるため、HTMLや空白を1文字でも出力した後では設定できません。エラーメッセージに出力が始まったファイルと行番号が書かれているので、その場所を確認します。PHPファイル先頭の <?php 前の空白や、ファイル末尾の ?> の後の改行、UTF-8のBOMがよくある原因です。
ページの読み込みが順番待ちになるファイル方式のセッションは、session_start() から処理の終了までファイルをロックします。同じ利用者のAjaxリクエストが並列に来ると、1つずつしか処理されません。セッションへの書き込みが終わった時点で session_write_close() を呼ぶと、ロックを早く外せます。
ログイン状態がすぐ切れる、保持されないブラウザの開発者ツールでCookieを見て、セッションIDのCookieが保存されているか、secure が付いているのにHTTPでアクセスしていないか、domain や path が想定どおりかを確認します。複数台のサーバーで負荷分散している場合は、セッションの保存先をRedisなどの共有ストアにする必要があります。
WordPressでPHPセッションを使うときの注意
WordPressのコアは、ログイン状態の管理にPHPセッションを使っていません。ログインCookie(wordpress_logged_in_ で始まる名前など)と、ユーザーごとに保存されるセッショントークンで管理しています。そのため、プラグインやテーマで session_start() を呼ぶと、次のような問題が起きることがあります。
- セッションのCookieがページキャッシュを無効にしたり、逆にキャッシュされたページで別人の状態が表示されたりする
- セッションのロックでREST APIやループバックリクエストが待たされ、サイトヘルスでPHPセッションに関する警告が出る
WordPressでは、ログイン中のユーザーに結びつく値はユーザーメタ(update_user_meta())、短時間だけ持つ値はトランジェント(set_transient())に保存するのが一般的です。どうしても独自のCookieが必要なら、COOKIEPATH・COOKIE_DOMAIN 定数と is_ssl() を使って範囲と属性をWordPressに合わせます。WooCommerceのように独自のセッション機構を持つプラグインもあるため、それらを使っている場合はその仕組みに従います。
Cookieの利用目的によっては、プライバシーポリシーへの記載や同意の取得が必要になります。サードパーティCookieの扱いはブラウザごとに方針が変わっているため(2026年10月現在)、外部サービスを埋め込む場合は各ブラウザと各サービスの最新情報を確認してください。
まとめ
- 改ざんされて困る値はセッションに、表示設定程度の値はCookieに置く。Cookieの値は信用せず許可リストで照合する
- Cookieとセッションには
Secure・HttpOnly・SameSiteを付け、session.use_strict_modeを有効にする - ログイン直後に
session_regenerate_id(true)、ログアウトでは中身の消去・Cookie削除・session_destroy()を行う - 「headers already sent」は出力の開始位置を、動作の遅さはセッションのロックを疑う
- WordPressではPHPセッションを避け、ユーザーメタやトランジェントで代替する