[PHP]session_set_cookie_params()完全解説|セッションCookieのセキュリティ設定と実践的な使い方

PHP

はじめに

PHPのセッションはブラウザのCookieを通じてセッションIDを管理します。このCookieの設定が甘いと、XSS(クロスサイトスクリプティング)によるセッションIDの盗難・CSRF攻撃・中間者攻撃など深刻なセキュリティリスクに直結します。

session_set_cookie_params() は、セッションIDを格納するCookieの有効期限・パス・ドメイン・Secure・HttpOnly・SameSiteといった属性を一括設定できる関数です。php.ini での設定を実行時に上書きできるため、アプリケーションレベルで確実にセキュリティ設定を適用できます。本番環境で必ず設定すべき関数の一つです。


関数の概要

項目内容
関数名session_set_cookie_params()
所属PHP セッション関数
導入バージョンPHP 4以降(配列形式は PHP 7.3以降)
PHP 8.x対応済み

構文

PHP 7.3以降では配列形式(推奨)と従来の引数形式の2種類があります。

配列形式(PHP 7.3以降・推奨)

session_set_cookie_params(array $options): bool

引数形式(PHP 4以降・後方互換)

session_set_cookie_params(
    int    $lifetime_or_options,
    string $path    = "",
    string $domain  = "",
    bool   $secure  = false,
    bool   $httponly = false
): bool

戻り値

  • 成功時:true
  • 失敗時:false

⚠️ 注意session_start() を呼び出したに設定しても反映されません。必ず session_start()に呼び出してください。


設定できるパラメータ一覧

パラメータデフォルト説明
lifetimeint0Cookie の有効期限(秒)。0 はブラウザ終了まで
pathstring"/"Cookie が有効なパス
domainstring""Cookie が有効なドメイン
secureboolfalsetrue にすると HTTPS のみで送信
httponlyboolfalsetrue にすると JavaScript からアクセス不可(XSS対策)
samesitestring"""Strict" / "Lax" / "None"(CSRF対策)

SameSite の使い分け

動作推奨シーン
Strict同一サイトのリクエストのみCookieを送信セキュリティ最優先(外部リンクからのログイン状態不要)
Lax安全なHTTPメソッド(GETなど)の外部遷移は許可一般的なWebアプリの推奨値
Noneクロスサイトでも常に送信(Secure 必須)埋め込みiframeや外部サービス連携

基本的な使い方

<?php
// 配列形式(PHP 7.3以降・推奨)
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'domain'   => 'example.com',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

session_start();
<?php
// 引数形式(後方互換)
session_set_cookie_params(
    0,              // lifetime
    '/',            // path
    'example.com',  // domain
    true,           // secure
    true            // httponly
);
session_start();

現在の設定を取得する

session_set_cookie_params() に対応する取得関数として session_get_cookie_params() があります。

<?php
$params = session_get_cookie_params();
print_r($params);
/*
Array
(
    [lifetime] => 0
    [path]     => /
    [domain]   =>
    [secure]   =>
    [httponly] =>
    [samesite] =>
)
*/

実践的なクラスベースの使用例

例1:OWASP推奨設定を一括適用するセキュアセッションクラス

<?php
/**
 * OWASP のセッション管理推奨設定を一括適用するクラス
 * 本番環境で最低限必要なCookieセキュリティ設定を確実に適用する
 */
class SecureCookieSessionInitializer
{
    private array $options;

    public function __construct(array $overrides = [])
    {
        $this->options = array_merge([
            'lifetime' => 0,          // ブラウザ終了まで(永続Cookieは非推奨)
            'path'     => '/',
            'domain'   => '',
            'secure'   => true,       // HTTPS必須
            'httponly' => true,       // JavaScript からのアクセスを禁止(XSS対策)
            'samesite' => 'Lax',      // CSRF対策
        ], $overrides);
    }

    public function initialize(string $sessionName = 'AppSession'): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            throw new \RuntimeException('セッション開始後は Cookie パラメータを変更できません');
        }

        session_name($sessionName);

        $result = session_set_cookie_params($this->options);
        if (!$result) {
            throw new \RuntimeException('session_set_cookie_params() の設定に失敗しました');
        }

        session_start();

        // 新規セッションの場合はIDを再生成してセッション固定攻撃を防ぐ
        if (!isset($_SESSION['_initialized'])) {
            session_regenerate_id(true);
            $_SESSION['_initialized'] = true;
        }

        echo "セッション初期化完了\n";
        $this->printParams();
    }

    public function printParams(): void
    {
        $params = session_get_cookie_params();
        echo "=== Cookie パラメータ ===\n";
        foreach ($params as $key => $value) {
            $display = is_bool($value) ? ($value ? 'true' : 'false') : $value;
            printf("  %-12s: %s\n", $key, $display);
        }
    }
}

$session = new SecureCookieSessionInitializer([
    'domain'   => '.example.com',   // サブドメイン共有
    'samesite' => 'Strict',
]);
// $session->initialize('MyShopSession');

/*
出力例:
セッション初期化完了
=== Cookie パラメータ ===
  lifetime    : 0
  path        : /
  domain      : .example.com
  secure      : true
  httponly    : true
  samesite    : Strict
*/

例2:環境別セキュリティレベル設定クラス

<?php
/**
 * 環境(本番・ステージング・開発)に応じて
 * Cookie のセキュリティレベルを自動調整するクラス
 * 開発環境では HTTPS なしでも動作するよう secure を緩める
 */
class EnvironmentAwareCookieConfigurator
{
    private array $presets;

    public function __construct()
    {
        $this->presets = [
            'production' => [
                'lifetime' => 0,
                'path'     => '/',
                'secure'   => true,
                'httponly' => true,
                'samesite' => 'Strict',
            ],
            'staging' => [
                'lifetime' => 0,
                'path'     => '/',
                'secure'   => true,
                'httponly' => true,
                'samesite' => 'Lax',
            ],
            'development' => [
                'lifetime' => 86400,   // 開発中は1日有効にして使い勝手を向上
                'path'     => '/',
                'secure'   => false,   // HTTP でも動作するよう緩める
                'httponly' => true,
                'samesite' => 'Lax',
            ],
            'testing' => [
                'lifetime' => 3600,
                'path'     => '/',
                'secure'   => false,
                'httponly' => false,   // テストツールからアクセスできるよう緩める
                'samesite' => 'Lax',
            ],
        ];
    }

    public function apply(string $environment, string $domain = ''): void
    {
        if (!isset($this->presets[$environment])) {
            throw new \InvalidArgumentException("未定義の環境: {$environment}");
        }

        if (session_status() === PHP_SESSION_ACTIVE) {
            throw new \RuntimeException('セッション開始後は変更できません');
        }

        $options = $this->presets[$environment];
        if ($domain !== '') {
            $options['domain'] = $domain;
        }

        session_set_cookie_params($options);

        echo "環境: {$environment}\n";
        echo "secure  : " . ($options['secure']   ? 'true'  : 'false') . "\n";
        echo "httponly: " . ($options['httponly']  ? 'true'  : 'false') . "\n";
        echo "samesite: {$options['samesite']}\n";

        if ($environment !== 'production') {
            echo "⚠️ 本番環境以外のプリセットを使用しています\n";
        }
    }

    public function getPreset(string $environment): array
    {
        return $this->presets[$environment] ?? [];
    }
}

$configurator = new EnvironmentAwareCookieConfigurator();
$configurator->apply('development');
// session_start();

例3:ログイン後に Cookie 有効期限を延長するクラス

<?php
/**
 * 「ログイン状態を保持する」チェックボックスに応じて
 * Cookie の lifetime を動的に切り替えるクラス
 */
class RememberMeCookieManager
{
    private const SHORT_LIFETIME  = 0;          // ブラウザ終了まで
    private const LONG_LIFETIME   = 30 * 86400; // 30日間

    public function __construct(
        private readonly string $domain = '',
        private readonly string $path   = '/'
    ) {}

    /**
     * ログイン時に「記憶する」オプションに応じて Cookie を設定する
     */
    public function setupForLogin(bool $rememberMe): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            // 既存セッションのCookieを再送してlifeimeを更新
            $this->reissueCookie($rememberMe);
            return;
        }

        $lifetime = $rememberMe ? self::LONG_LIFETIME : self::SHORT_LIFETIME;

        session_set_cookie_params([
            'lifetime' => $lifetime,
            'path'     => $this->path,
            'domain'   => $this->domain,
            'secure'   => true,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);

        session_start();
        session_regenerate_id(true);

        $_SESSION['remember_me']  = $rememberMe;
        $_SESSION['login_at']     = time();
        $_SESSION['expires_at']   = $rememberMe ? time() + self::LONG_LIFETIME : null;

        echo "ログイン Cookie 設定\n";
        echo "記憶する: " . ($rememberMe ? 'YES(30日間)' : 'NO(ブラウザ終了まで)') . "\n";
    }

    /**
     * セッション開始後に Cookie を再発行して有効期限を更新する
     */
    private function reissueCookie(bool $rememberMe): void
    {
        $lifetime = $rememberMe ? self::LONG_LIFETIME : self::SHORT_LIFETIME;
        $params   = session_get_cookie_params();

        setcookie(
            session_name(),
            session_id(),
            [
                'expires'  => $lifetime > 0 ? time() + $lifetime : 0,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'],
            ]
        );

        echo "Cookie 再発行(セッション開始後)\n";
    }

    /**
     * ログアウト時に Cookie を完全削除する
     */
    public function clearLoginCookie(): void
    {
        $params = session_get_cookie_params();
        setcookie(
            session_name(), '',
            [
                'expires'  => time() - 86400,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'],
            ]
        );

        $_SESSION = [];
        session_destroy();
        echo "ログアウト:Cookie を削除しました\n";
    }
}

$manager = new RememberMeCookieManager(domain: '.example.com');
// $manager->setupForLogin(rememberMe: true);

例4:サブドメイン共有セッション設定クラス

<?php
/**
 * shop.example.com / admin.example.com など
 * 複数サブドメイン間でセッションを共有するための設定クラス
 * ドメイン設定のルールを安全に管理する
 */
class SubdomainSharedSessionConfigurator
{
    private string $rootDomain;

    public function __construct(string $rootDomain)
    {
        // 先頭の "." は自動付与するので不要
        $this->rootDomain = ltrim($rootDomain, '.');
    }

    /**
     * サブドメイン共有向け Cookie パラメータを設定する
     * domain を ".example.com" にすることで全サブドメインで有効になる
     */
    public function configure(
        bool   $shareAcrossSubdomains = true,
        string $restrictToPath        = '/'
    ): void {
        if (session_status() === PHP_SESSION_ACTIVE) {
            throw new \RuntimeException('セッション開始後は変更できません');
        }

        // サブドメイン共有:先頭に "." を付ける
        $domain = $shareAcrossSubdomains
            ? '.' . $this->rootDomain
            : $this->rootDomain;

        session_set_cookie_params([
            'lifetime' => 0,
            'path'     => $restrictToPath,
            'domain'   => $domain,
            'secure'   => true,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);

        echo "セッション共有設定\n";
        echo "ドメイン: {$domain}\n";
        echo "パス制限: {$restrictToPath}\n";
        echo "サブドメイン共有: " . ($shareAcrossSubdomains ? 'YES' : 'NO') . "\n";

        $this->printWarnings($shareAcrossSubdomains);
    }

    private function printWarnings(bool $shared): void
    {
        if ($shared) {
            echo "⚠️ 全サブドメインでセッションが共有されます。\n";
            echo "   信頼できないサブドメインが存在する場合はセキュリティリスクがあります。\n";
        }
    }

    /**
     * 特定パス配下のみでセッションを有効にする(管理画面など)
     */
    public function configureForAdminPanel(): void
    {
        $this->configure(shareAcrossSubdomains: false, restrictToPath: '/admin');
    }
}

$configurator = new SubdomainSharedSessionConfigurator('example.com');
$configurator->configure(shareAcrossSubdomains: true);
// session_start();

/*
出力例:
セッション共有設定
ドメイン: .example.com
パス制限: /
サブドメイン共有: YES
⚠️ 全サブドメインでセッションが共有されます。
   信頼できないサブドメインが存在する場合はセキュリティリスクがあります。
*/

例5:SameSite=None の外部サービス連携設定クラス

<?php
/**
 * iFrame 埋め込みや外部サービス連携(OAuth コールバックなど)で
 * SameSite=None が必要な場合の設定クラス
 * None は Secure 必須・古いブラウザ対策も含む
 */
class CrossSiteCookieConfigurator
{
    /**
     * SameSite=None + Secure の設定を適用する
     * 外部サービス連携・iFrame 埋め込み用
     */
    public function applyNoneMode(): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            throw new \RuntimeException('セッション開始後は変更できません');
        }

        if (!$this->isHttps()) {
            throw new \RuntimeException(
                'SameSite=None は HTTPS 環境でのみ使用できます(Secure 属性が必須)'
            );
        }

        session_set_cookie_params([
            'lifetime' => 0,
            'path'     => '/',
            'secure'   => true,   // SameSite=None には必須
            'httponly' => true,
            'samesite' => 'None',
        ]);

        echo "SameSite=None 設定完了(クロスサイト Cookie 有効)\n";
        echo "⚠️ Secure 属性が必須です。HTTPS 環境でのみ有効です。\n";
    }

    /**
     * ユーザーエージェントが SameSite=None をサポートするか確認する
     * 古いブラウザ(iOS 12・Chrome 51-66 など)では None が無効扱いになる
     */
    public function isSameSiteNoneCompatible(): bool
    {
        $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';

        // iOS 12 系は SameSite=None を SameSite=Strict として解釈する
        if (preg_match('/\(iP.+; CPU .*OS 12[_\d]*.*\) AppleWebKit\//', $ua)) {
            return false;
        }

        // Chrome 51-66 は SameSite=None を認識しない
        if (preg_match('/Chrom[^ \/]+\/(?:5[1-9]|6[0-6])[\.\d]* /', $ua)) {
            return false;
        }

        return true;
    }

    /**
     * ブラウザ互換性を考慮して最適な SameSite を返す
     */
    public function getCompatibleSameSite(string $preferred = 'None'): string
    {
        if ($preferred === 'None' && !$this->isSameSiteNoneCompatible()) {
            echo "互換性のためフォールバック: None → Lax\n";
            return 'Lax';
        }
        return $preferred;
    }

    private function isHttps(): bool
    {
        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || ($_SERVER['SERVER_PORT'] ?? 80) == 443;
    }
}

$configurator = new CrossSiteCookieConfigurator();
$samesite = $configurator->getCompatibleSameSite('None');
echo "適用 SameSite: {$samesite}\n";

例6:Cookie パラメータのセキュリティ監査クラス

<?php
/**
 * 現在の Cookie パラメータ設定を監査し、
 * セキュリティ上の問題点を一覧表示するクラス
 * デプロイ時のチェックや管理画面でのヘルスチェックに使用する
 */
class CookieSecurityAuditor
{
    public function audit(): array
    {
        $params   = session_get_cookie_params();
        $findings = [];

        // secure チェック
        if (!$params['secure']) {
            $findings[] = [
                'level'   => 'HIGH',
                'param'   => 'secure',
                'current' => 'false',
                'advice'  => 'HTTPS 環境では true に設定してください(中間者攻撃対策)',
            ];
        }

        // httponly チェック
        if (!$params['httponly']) {
            $findings[] = [
                'level'   => 'HIGH',
                'param'   => 'httponly',
                'current' => 'false',
                'advice'  => 'true に設定してください(XSS によるセッションID盗難対策)',
            ];
        }

        // samesite チェック
        $samesite = strtolower($params['samesite'] ?? '');
        if ($samesite === '') {
            $findings[] = [
                'level'   => 'MEDIUM',
                'param'   => 'samesite',
                'current' => '(未設定)',
                'advice'  => '"Lax" または "Strict" を設定してください(CSRF対策)',
            ];
        } elseif ($samesite === 'none' && !$params['secure']) {
            $findings[] = [
                'level'   => 'HIGH',
                'param'   => 'samesite + secure',
                'current' => 'None(secure=false)',
                'advice'  => 'SameSite=None を使用する場合は secure=true が必須です',
            ];
        }

        // lifetime チェック
        if ($params['lifetime'] > 0) {
            $days = round($params['lifetime'] / 86400, 1);
            if ($days > 30) {
                $findings[] = [
                    'level'   => 'LOW',
                    'param'   => 'lifetime',
                    'current' => "{$params['lifetime']} 秒(約 {$days} 日)",
                    'advice'  => 'Cookie の有効期限が長すぎます。30日以内を推奨します',
                ];
            }
        }

        return [
            'params'   => $params,
            'findings' => $findings,
            'score'    => $this->calcScore($findings),
        ];
    }

    private function calcScore(array $findings): string
    {
        $highCount = count(array_filter($findings, fn($f) => $f['level'] === 'HIGH'));
        if ($highCount > 0) return "❌ 要改善(HIGH: {$highCount}件)";
        if (!empty($findings)) return "⚠️ 注意あり";
        return "✅ 問題なし";
    }

    public function printReport(): void
    {
        $result = $this->audit();

        echo "=== Cookie セキュリティ監査レポート ===\n";
        echo "総合評価: {$result['score']}\n\n";

        echo "--- 現在の設定 ---\n";
        foreach ($result['params'] as $key => $value) {
            $display = is_bool($value) ? ($value ? 'true' : 'false') : ($value ?: '(未設定)');
            printf("  %-12s: %s\n", $key, $display);
        }

        if (!empty($result['findings'])) {
            echo "\n--- 指摘事項 ---\n";
            foreach ($result['findings'] as $i => $finding) {
                echo "\n[{$finding['level']}] {$finding['param']}\n";
                echo "  現在値: {$finding['current']}\n";
                echo "  対応策: {$finding['advice']}\n";
            }
        } else {
            echo "\n指摘事項はありません。\n";
        }
    }
}

$auditor = new CookieSecurityAuditor();
$auditor->printReport();

/*
出力例(デフォルト設定で監査した場合):
=== Cookie セキュリティ監査レポート ===
総合評価: ❌ 要改善(HIGH: 2件)

--- 現在の設定 ---
  lifetime    : 0
  path        : /
  domain      : (未設定)
  secure      : false
  httponly    : false
  samesite    : (未設定)

--- 指摘事項 ---

[HIGH] secure
  現在値: false
  対応策: HTTPS 環境では true に設定してください(中間者攻撃対策)

[HIGH] httponly
  現在値: false
  対応策: true に設定してください(XSS によるセッションID盗難対策)

[MEDIUM] samesite
  現在値: (未設定)
  対応策: "Lax" または "Strict" を設定してください(CSRF対策)
*/

関連関数との比較

関数役割
session_set_cookie_params()セッションCookieのパラメータを設定する
session_get_cookie_params()現在のセッションCookieパラメータを取得する
session_name()セッションIDを格納するCookie名を取得・変更する
setcookie()任意のCookieを設定する(セッションIDの再発行時などに使用)
ini_set('session.cookie_*', ...)php.ini レベルでCookieパラメータを設定する(同等)

よくある落とし穴

<?php
// ❌ NG:session_start() 後に呼んでも反映されない
session_start();
session_set_cookie_params(['httponly' => true]); // 無視される

// ✅ OK:session_start() の前に設定する
session_set_cookie_params(['httponly' => true, 'secure' => true, 'samesite' => 'Lax']);
session_start();
<?php
// ❌ 注意:SameSite=None なのに secure=false は無効(ブラウザが拒否する)
session_set_cookie_params([
    'samesite' => 'None',
    'secure'   => false, // ← これだと Cookie が送信されない
]);

// ✅ SameSite=None は必ず secure=true とセットで
session_set_cookie_params([
    'samesite' => 'None',
    'secure'   => true,
]);
<?php
// ❌ よくある誤解:lifetime=0 は「Cookie なし」ではなく「ブラウザ終了まで有効」
session_set_cookie_params(['lifetime' => 0]);
// → セッションCookieとして発行される(ブラウザを閉じると消える)

// 「30日間有効」にしたい場合
session_set_cookie_params(['lifetime' => 30 * 86400]);

まとめ

パラメータ推奨値(本番)理由
lifetime0ブラウザ終了で失効。永続化は必要な場合のみ
path'/'アプリ全体で有効
domain'.example.com'サブドメイン共有が必要な場合のみドメイン指定
securetrueHTTPS でのみ送信(必須)
httponlytrueJS からのID盗難を防ぐ(必須)
samesite'Lax' または 'Strict'CSRF対策(必須)

session_set_cookie_params() はわずかな設定でXSS・CSRF・中間者攻撃という三大脅威に対して一定の防御を確立できる、コストパフォーマンスの高いセキュリティ施策です。secure / httponly / samesite の3つを正しく設定するだけで、セッションのセキュリティは大きく向上します。


参考リンク

タイトルとURLをコピーしました