【コピペで使える】PHPとMySQLで作る安全なログイン機能の実装サンプル(解説付き)

会員制サイトやWebアプリケーションをスクラッチで開発する際、必ず必要になるのが「ログイン機能」です。 LaravelやCakePHPなどのフレームワークを使えば簡単に実装できますが、仕組みを深く理解するため、あるいは軽量なシステムを作るために「PHPで一からスクラッチ開発したい」というケースも多いのではないでしょうか。

しかし、ログイン機能は一歩間違えると重大なセキュリティ脆弱性を生む危険な機能でもあります。

そこで今回は、LAMP環境(PHP/MySQL)において、「最小限の構成でありながら、実務でも通用する安全なセキュリティ対策を施したログイン機能」のサンプルコードを用意しました。 コピペで動作を確認できる一連のファイル一式を、詳しい解説付きでご紹介します。

主なプログラム構成と動作

今回のログイン機能は、以下の5つのファイル(データベース設定+4つのプログラム)で構成されています。

├── config.php      # データベース接続情報の定数定義
├── database.php    # DB接続・ユーザー認証処理(関数定義)
├── login.php       # ログイン画面 兼 処理受付
├── logout.php      # ログアウト処理
└── auth.php        # ログイン状態のチェック(各会員ページで使い回す用)

全体の動作イメージ

  • ・ユーザーが「login.php」からメールアドレスとパスワードを送信。
  • ・「database.php」を介してDB内のハッシュ化されたパスワードと照合。
  • ・認証成功後、セッションIDを再生成して「auth.php」でログイン状態を保持。
  • ・ログアウト時は「logout.php」でセッションを完全に破棄。

データベース(accountsテーブル)の準備

まずはユーザー情報を保存するデータベースを準備します。必要最低限のカラム構成ですが、パスワードを暗号化(ハッシュ化)して保存するため、password カラムの桁数は余裕を持って varchar(255) で定義します。


CREATE TABLE `accounts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `email` varchar(255) NOT NULL UNIQUE,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

セキュリティのポイント:パスワードは絶対に「平文」で保存しない

ユーザーが登録したパスワードをそのまま(平文で)DBに保存するのは非常に危険です。万が一DBが流出した際、二次被害を招きます。本サンプルでは、PHPの安全なハッシュ化関数 password_hash() で暗号化したパスワードがこのテーブルに入っていることを前提としています。

データベース接続設定(config.php)

データベースへの接続情報を管理する共通ファイルです。環境に合わせて書き換えてください。

<?php
// データベース接続情報
define('DB_HOST', 'localhost');
define('DB_NAME', 'あなたのデータベース名');
define('DB_USER', 'あなたのユーザー名');
define('DB_PASS', 'あなたのパスワード');

データベースアクセスファイル(database.php)

PDOを使ってデータベースに接続し、ユーザーの認証処理を行う関数をまとめたファイルです。

<?php
require_once __DIR__ . '/config.php';

/**
 * データベース接続オブジェクトを返す
 */
function getDbConnection() {
    try {
        $dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4';
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // エラー時に例外をスロー
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 連想配列で取得
            PDO::ATTR_EMULATE_PREPARES => false, // プリペアドステートメントのエミュレーションを無効化
        ];
        return new PDO($dsn, DB_USER, DB_PASS, $options);
    } catch (PDOException $e) {
        // 実務ではログに出力するなどの対策を行います
        exit('データベース接続エラーが発生しました。');
    }
}

/**
 * ユーザーのログイン認証処理
 */
function login($email, $password) {
    $db = getDbConnection();
    
    // SQLインジェクション対策としてプリペアドステートメントを使用
    $sql = "SELECT * FROM accounts WHERE email = :email";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':email', $email, PDO::PARAM_STR);
    $stmt->execute();
    
    $account = $stmt->fetch();
    
    // ユーザーが存在し、パスワードが一致するか検証
    if ($account && password_verify($password, $account['password'])) {
        // セキュリティのため、セッションにパスワードは含めない
        unset($account['password']);
        return $account;
    }
    
    return false;
}

SEO・技術解説のポイント

SQLインジェクション対策

ユーザー入力をそのままSQL文に結合せず、prepare()bindParam() を用いることで、データベースの不正操作を防ぎます。

password_verify()の使用

平文のパスワードと、DBに保存されたハッシュ化パスワードを安全に比較するPHP標準の関数です。

ログイン認証・状態チェックファイル(auth.php)

<?php
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

// セッションにアカウント情報があるかチェック
if (isset($_SESSION['account'])) {
    // ログインフラグを立てる
    $login = true;
    $user = $_SESSION['account'];
} else {
    // ログインしていない場合
    $login = false;
}

ログイン画面(login.php)

実際にユーザーがメールアドレスとパスワードを入力する画面です。認証処理の受付もこのファイルで行います。

<?php
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

require_once __DIR__ . '/database.php';
require_once __DIR__ . '/auth.php';

// すでにログイン済みの場合はマイページやトップ等にリダイレクト
if ($login) {
    // header("Location: ./dashboard.php"); 
    // exit;
}

$errorMessage = "";

// ログインボタンが押された時の処理
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mode']) && $_POST['mode'] === 'login') {
    
    $email = filter_input(INPUT_POST, 'email', FILTER_DEFAULT);
    $password = filter_input(INPUT_POST, 'password', FILTER_DEFAULT);

    if (!empty($email) && !empty($password)) {
        // 認証処理の実行
        $account = login($email, $password);
        
        if ($account) {
            // セッション固定攻撃(Session Fixation)対策:セッションIDの再生成
            session_regenerate_id(true);
            
            // セッションにユーザー情報を格納
            $_SESSION['account'] = $account;
            
            // 自ページにリダイレクトして再読み込み(二重送信防止)
            header("Location: ./login.php");
            exit;
        } else {
            $errorMessage = "メールアドレスまたはパスワードが正しくありません。";
        }
    } else {
        $errorMessage = "メールアドレスとパスワードを入力してください。";
    }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ログイン</title>
</head>
<body>

<?php if ($login): ?>
    <h2>ログインしました</h2>
    <p>ようこそ、<?php echo htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8'); ?> さん</p>
    <p><a href="./logout.php">ログアウトする</a></p>
<?php else: ?>
    <h2>ログインフォーム</h2>
    
    <?php if (!empty($errorMessage)): ?>
        <p style="color: red;"><?php echo htmlspecialchars($errorMessage, ENT_QUOTES, 'UTF-8'); ?></p>
    <?php endif; ?>

    <form action="./login.php" method="POST">
        <div>
            <label for="email">メールアドレス:</label>
            <input type="email" id="email" name="email" required placeholder="example@example.com">
        </div>
        <br>
        <div>
            <label for="password">パスワード:</label>
            <input type="password" id="password" name="password" required placeholder="パスワードを入力">
        </div>
        <br>
        <input type="hidden" name="mode" value="login">
        <input type="submit" value="ログイン">
    </form>
<?php endif; ?>

</body>
</html>

 

セキュリティ・コーディングのポイント

セッション固定攻撃対策 (session_regenerate_id(true))

ログイン成功直後にセッションIDを新しく作り直すことで、悪意ある第三者にセッションを乗っ取られる(セッションハイジャック)のを防ぎます。

XSS(クロスサイトスクリプティング)対策

画面にユーザー名やエラーメッセージを表示する際は、必ず htmlspecialchars() を通してサニタイズ(無害化)します。

HTMLの適切なマークアップ

<form> タグや <label> タグを正しく配置し、コピペしただけでフォームが崩れず動作するように整形しました。

ログアウト画面(logout.php)

ログアウト処理は、サーバー側に保存されているユーザーのセッション情報を完全に消去する処理を行います。

<?php
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

// セッション変数をすべて解除
$_SESSION = array();

// クッキーに保存されているセッションIDも削除
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// 最終的にセッションを破棄
session_destroy();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ログアウト完了</title>
</head>
<body>
    <h2>ログアウトしました</h2>
    <p><a href="./login.php">ログイン画面へ戻る</a></p>
</body>
</html>

技術解説のポイント

単に session_destroy() を呼ぶだけでなく、ブラウザ側のクッキーに残っているセッションIDも合わせてクリアするのが、PHPにおける最も確実で安全なログアウト処理の手順です。

スクラッチ開発での注意点

今回紹介したサンプルコードは、ログイン機能の「骨組み」として安全に動作するミニマルな構成です。

実際の商用Webアプリケーションとして運用する際には、さらに以下のような対策を組み合わせることでセキュリティを強固にできます。

  • 常時SSL化(HTTPS):通信経路でのパスワード盗聴を防ぎます。
  • アカウントロック機能:連続してログインに失敗したIPアドレスやアカウントを一定時間凍結し、ブルートフォースアタック(総当たり攻撃)を防ぎます。
  • CSRF(クロスサイトリクエストフォージェリ)対策:フォーム送信時にワンタイムトークンを発行・検証します。

まずはこのシンプルな構成をローカル環境(XAMPPやMAMPなど)にコピペして、どのようにセッション情報が動き、認証が行われるのかを体験してみてください。

もっと手軽に会員機能を導入しませんか?

HTMLサイトに貼るだけで会員化できる「ログインタス」

PHPやDBの面倒な設定は一切不要。今あるHTMLファイルに専用コードを貼り付けるだけで、月額0円から高機能なログイン機能・会員管理が実装可能です。

ログインタスを詳しく見る

※外部サイトへ移動します

投稿者プロフィール

たーさん代表者
東海大学工学部を卒業後、東芝情報システム株式会社(旧グループ会社含む)に入社。半導体(SRAM)の開発チームにて、Unix環境でのPerlを用いた業務自動化プログラムの開発など、エンジニアとしての確かな基礎と緻密な論理的思考力を培う。

その後、東証上場企業である株式会社ザッパラスへ。Web・モバイルの最前線で、数多くのユーザーに愛されるコンテンツ制作業務に従事。ここで「ユーザー目線に立った魅力的なWebコンテンツの企画・制作ノウハウ」を深く学ぶ。

開発・システム側から見た「堅牢なロジック」と、制作・ユーザー側から見た「伝わるコンテンツ」の双方を実務で経験した強みを活かし、フリーランスとして独立。

現在は、ウイングアーク1st株式会社の「データのじかん」運営チームに参画するなど、大手・中小企業のWeb運営・開発パートナーとして活動。完全在宅でありながら、固定IPの完備や厳格なセキュリティポリシーの遵守を徹底し、企業のインフラや本番環境を安全に支える「チームの一員」として高い信頼を得ています。

見た目の美しさはもちろんのこと、内部の構造やWordPressのカスタマイズ、運用・セキュリティまでを見据えた、技術的バックボーンのある高品質なWebサイト・システム制作をワンストップでご提供しています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA