バックエンド 2026.06.23

PHPのセッションとCookieを正しく使い分ける!認証バグを防ぐ方法

約4分で読めます

中小企業の業務システムで頻発するPHPの認証バグ。セッションとCookieの混同が原因のケースが多い。正しい使い分けと実装パターンを解説します。

こんな悩み、ありませんか?

「ログインしたはずなのに、しばらくするとセッションが切れる」「別のユーザーでログインしたら、なぜか前のユーザーの情報が表示された」「本番環境に上げたらログインが通らない……」

業務システムの保守を担当するWeb担当者から、こういった相談が定期的に届きます。これらのトラブルの多くは、PHPのセッションとCookieの違いを曖昧に理解したまま実装してしまったことが根本原因です。

特に中小企業の業務システムは、昔に外注して作ったコードをそのまま使い続けていたり、複数の担当者がパッチを当て続けたりしていることが多い。気がつけば「誰も全体を把握していない認証まわり」になっているケースは珍しくありません。

この記事では、セッションとCookieの本質的な違いから、実際に使うべき場面の判断基準、そしてよくある実装ミスのパターンまでを、コード例を交えながら整理します。


セッションとCookieは「何が違うのか」を正確に理解する

まず前提として、両者の役割の違いを押さえておきましょう。

項目セッションCookie
データの保存場所サーバー側クライアント(ブラウザ)側
有効期限のデフォルトブラウザを閉じるまで設定次第で長期保持が可能
容量の上限サーバーリソースに依存約4KB
セキュリティリスク相対的に低い改ざん・盗聴のリスクあり
主な用途ログイン状態・一時データユーザー設定・自動ログイン

セッションはサーバー側にデータを持ち、ブラウザにはそのIDだけを渡します。Cookieはデータそのものをブラウザに保存します。この違いが、セキュリティ要件の高い認証処理において致命的な差を生むことがあります。

認証情報(ユーザーIDや権限情報)は必ずセッションに保存するというのが基本原則です。Cookieに直接保存することは、データの改ざんリスクを生む行為です。


バックエンド開発でお困りですか?

API設計・DB最適化・システム構築など、ご相談ください

無料で相談する

よくある認証バグの原因:実案件から見えたパターン

神奈川の製造業クライアントから相談を受けた案件での実例をご紹介します。社内向けの受注管理システムで、特定のユーザーがログインすると別のユーザーのデータが見えてしまうという深刻なバグが発生していました。

調査してみると、問題のコードはこのようなものでした。

// ❌ 問題のある実装例
setcookie('user_id', $userId, time() + 3600);
setcookie('role', $userRole, time() + 3600);

// 認証チェック
$userId = $_COOKIE['user_id'];
$role = $_COOKIE['role'];

CookieにユーザーIDと権限を直接書き込んでいたため、ブラウザの開発者ツールから値を書き換えるだけで、別ユーザーとして振る舞える状態になっていたのです。さらに、CookieのHttpOnly属性が設定されておらず、JavaScriptからもアクセスできる状態でした。

これは単なる実装ミスではなく、設計段階での認識不足が原因です。なぜその方法を選んだか確認したところ、「セッションだとサーバーに負荷がかかると聞いた」という理由でした。小規模な業務システムであれば、セッションの負荷を心配する必要はほぼありません。


正しい実装パターン:セッションと署名付きCookieの使い分け

基本:ログイン認証はセッションで管理する

Laravelを使っている場合は標準のセッション管理に任せるのが最善ですが、素のPHPで書かれたシステムの場合は以下のパターンを参考にしてください。

<?php
// セッションの開始(必ずページ冒頭で)
session_start();

// ログイン処理
function loginUser(int $userId, string $role): void {
    // セッションIDを再生成してセッション固定攻撃を防ぐ
    session_regenerate_id(true);

    $_SESSION['user_id'] = $userId;
    $_SESSION['role']    = $role;
    $_SESSION['login_at'] = time();
}

// 認証チェック
function isAuthenticated(): bool {
    if (empty($_SESSION['user_id'])) {
        return false;
    }
    // セッションタイムアウトチェック(30分)
    if (time() - $_SESSION['login_at'] > 1800) {
        session_destroy();
        return false;
    }
    // タイムスタンプを更新
    $_SESSION['login_at'] = time();
    return true;
}

応用:「次回から自動ログイン」はCookieを正しく使う

「ログイン状態を30日間保持する」のような機能は、Cookieを使う正当なユースケースです。ただし、CookieにユーザーIDを直接保存するのはNG。推測不可能なトークンを保存し、サーバー側でDBと突合する設計にします。

<?php
// ✅ 自動ログイン用トークンの発行
function issueRememberToken(int $userId, PDO $pdo): void {
    // 暗号学的に安全なランダムトークン生成
    $token  = bin2hex(random_bytes(32));
    $hashed = hash('sha256', $token);
    $expiry = date('Y-m-d H:i:s', time() + 86400 * 30);

    // DBにハッシュ化したトークンを保存
    $stmt = $pdo->prepare(
        'INSERT INTO remember_tokens (user_id, token_hash, expires_at)
         VALUES (?, ?, ?)'
    );
    $stmt->execute([$userId, $hashed, $expiry]);

    // Cookieにはトークンのみをセット
    setcookie(
        'remember_token',
        $token,
        [
            'expires'  => time() + 86400 * 30,
            'path'     => '/',
            'secure'   => true,   // HTTPS必須
            'httponly' => true,   // JavaScript から読めないように
            'samesite' => 'Lax',  // CSRF対策
        ]
    );
}

ポイントは3つです。①トークン自体はDBにハッシュ化して保存、②CookieにはHTTPS必須のsecure属性を付与、③HttpOnly属性でJavaScriptからのアクセスを遮断。この3点を外すと、脆弱なまま動作してしまいます。


よくある失敗パターンと対処法

PHPでセッションを使うには、`session_start()`をHTMLの出力より前に記述する必要があります。`echo`やHTMLタグの後ろに書くと「headers already sent」エラーが発生し、セッションが機能しません。インクルードしているファイルの先頭に余分な空白や改行がないかも要確認です。
セッションのデフォルト保存先はサーバーのファイルシステムです。本番環境でロードバランサーを使っていると、リクエストのたびに別サーバーに振られ、セッションが消えたように見えます。解決策はセッションをデータベースやRedisに保存することです。Laravelなら`SESSION_DRIVER=database`の設定一行で対応できます。
ログイン成功直後に`session_regenerate_id(true)`を呼ばないと、ログイン前後でセッションIDが変わらず、攻撃者が事前に仕込んだセッションIDを悪用できる「セッション固定攻撃」の餌食になります。ログイン処理には必須のステップです。
`setcookie()`の第3引数を単純なexpires時刻だけにしている古いコードが多く見られます。PHP7.3以降は配列形式で`secure`、`httponly`、`samesite`をまとめて指定できます。特に業務システムはHTTPS化と`secure`属性のセットが必須です。

セキュリティ設定のステータスを可視化する

実際の案件でセキュリティ診断を行うと、以下のような改善余地が見つかることが多いです。

上のレーダーチャートはあるクライアントの実際の診断イメージをもとに作成したものです。「動いているから問題ない」と思っているシステムが、セキュリティ面では複数の穴を抱えているケースは非常に多い。


バックエンド開発でお困りですか?

API設計・DB最適化・システム構築など、ご相談ください

無料で相談する

開発・運用でお困りなら

システム開発

設計から運用まで、堅牢なシステムを構築します

200件以上の制作実績 顧客満足度97% 初回相談無料

※ 通常1営業日以内にご返信します

まとめと次のステップ

PHPのセッションとCookieは、役割が似ているように見えて根本的に異なる仕組みです。「認証情報はサーバー側のセッションで管理する」「Cookieを使う場合は属性設定を徹底する」この2点を守るだけで、業務システムの認証まわりの脆弱性の大半は防げます。

既存システムのコードが今どういう状態になっているか、一度確認してみてください。特に「何年も触っていない認証まわりのコード」は、今の水準から見ると危険な実装になっていることが少なくありません。

自社システムの現状が気になった方は、Fivenine Designへコードレビューや改修のご相談をお気軽にどうぞ。神奈川を拠点に20年以上、中小企業の業務システム開発に携わってきた実績から、実用的な改善提案をお伝えできます。


実装確認チェックリスト

この記事をシェア

システム開発のご相談、受付中です

設計・開発・テスト・運用まで、ビジネスに合ったシステムを構築します。 初回相談は無料です。

※ 1営業日以内にご返信いたします

この技術でお困りなら

無料でプロに相談できます

相談する
AIに無料相談