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セッションを避け、ユーザーメタやトランジェントで代替する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲