バックエンド入門1 - ログイン機能を作る
静的サイトだけでなく、ユーザの操作に応じて内容が変わる、動的なWebアプリケーションを作ることを目指します。
今回は、ログイン処理の仕組みを理解することを目的とし、PHPで簡易的なログイン機能を実装します。
詳しい文法は、PHP References を適宜参照してください。
目次
前提知識
1-1. クライアントサーバアーキテクチャ
1-2. HTTP
1-3. 認証とセッション管理
PHPの文法
2-1. オブジェクト指向
2-2. 基本
2-3. Web開発で頻出
実装
3-1. データベースの設定
3-2. ログインページ・マイページの作成
3-3. ログイン処理
演習
1. 前提知識
1-1. クライアントサーバアーキテクチャ
3層クライアントサーバシステム
1990年代、2層クライアントサーバシステムが主流だった。
- 「クライアント ↔︎ DBサーバ」の2層
- アプリケーション(ビジネスロジック)が、クライアント側にUIと一体化して組み込まれる
しかし、次のような問題点があった。
- アプリケーション間での相互矛盾
- ビジネスロジックの変更に弱い
そこで、2000年代に、3層クライアントサーバシステムが生まれた。
- 「プレゼンテーション層(UI)、ファンクション層/AP層(処理)、データベースアクセス層」に分離
- ビジネスロジックをクライアント側からサーバ側に移行
この UI・ロジック・データ という3層アーキテクチャは、2000年代に誕生したWebアプリと相性が良い。
TIP
ネイティブアプリ
ビジネスロジックがクライアント側にある。PCにソフトをインストールし、LANでDBに接続する。ゲームアプリ, Discord(アプリ版)など。
Webアプリ
ビジネスロジックがサーバ側にある。1995年頃のブラウザの誕生による(当初は静的HTML専用だった)。Gmail, Xなど。
3層Webアーキテクチャ

→ 静的処理(HTML/CSS,画像)はWebサーバが、動的処理はAPサーバが行う。
代表的なWebサーバソフト
Webサーバとは、HTTPリクエストを受け取り、レスポンスを返すソフトウェア。
必要であれば、リクエストをAPサーバへ転送するリバースプロキシとしても動く。
Apache HTTP Server(1995〜)
- リクエスト処理はマルチプロセス型(1リクエスト=1プロセス)
- 高負荷時にリソース消費が大きい
Nginx(2004〜)
- リクエスト処理はイベント駆動型 → 多数の同時接続に強い
- 静的ファイルの配信が高速
- HTTPS処理、負荷分散
TIP
WebサーバとAPサーバを繋ぐ規格(プロトコル)
CGI(Common Gateway Interface)
昔のWeb。リクエストごとにプログラムを起動するため遅い。
FastCGI
サーバ常駐型。PHP-FPMで用いられる。
WSGI(Web Server Gateway Interface)
Python系。
ASGI(Asynchronous Server Gateway Interface)
WSGIの進化版で、非同期処理・WebSocketに対応。FastAPIで用いられる。
代表的なAPサーバ
ソースコードを元に、アプリケーションロジックを実行する。
PHP系
PHP-FPM(FastCGIプロセスマネージャ)。Laravel, Symfonyなどのフレームワークで使われる。
Python系
Gunicorn, uWSGI(WSGIサーバ)。Django, Flaskなどのフレームワークで使われる。
Uvicorn(ASGIサーバ)。FastAPIのライブラリで使われる。
Ruby系
Puma, Unicorn(Rackサーバ)。Railsで使われる。
現在は、HTTP受付・ロジック実行を一体でやることが多い。
Node.js(JavaScript のサーバ実行環境)
フレームワーク:Express, NestJS
Go
フレームワーク:Gin, Echo, Fiber
代表的なDBサーバ
RDB(リレーショナルデータベース)
垂直スケール。標準の使用言語はSQLで、各DBが独自機能・文法を足している。
- MySQL:LAMP構成(Linux, Apache, MySQL, PHP)の定番。
- PostgreSQL:データ型、拡張機能が豊富
- Oracle Database
- SQLite:サーバ不要の組み込みDB。データは単一ファイル。
NoSQL
水平スケール。
key: value型やJSON型などのドキュメントでデータを管理。- MongoDB
- Redis:高速アクセス → キャッシュに使われる。
TIP
垂直スケール
1台のDBサーバを強くする(CPU・メモリ↑)。
水平スケール
DBを複数台設けて、データを分散させて保存する(負荷分散)。
(補足)サーバはどこにある?
オンプレミス
自分たちの組織で物理サーバを持つ(@会社のサーバ室)。セキュリティポリシーを細かく制御できる。
クラウド(AWS, Microsoft Azure, Google Cloud)
物理サーバは、クラウド事業者のデータセンターにある。ユーザは、その物理サーバを分割して作られる仮想サーバ(VM)を借りる。
ソースコードは、APサーバのマシンに置かれる。
社内ネットワーク→インターネット→クラウドのネットワーク
1-2. HTTP(Hyper Text Transfer Protocol)
定義
WebサーバとWebクライアント(ブラウザ)間でWeb情報をやり取りするための通信プロトコル。1通信=リクエストとレスポンスの1往復。ステートレス(=前の状態を覚えない)なプロトコル。
TIP
プロトコル
コンピュータ同士が情報をやり取りする際に必要となる規則。
ブラウザで https://example.com/index.html にアクセスする際、以下のようなリクエストがサーバに送られている。
GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0)
Accept: text/html,application/xhtml+xml
Accept-Language: ja,en-US;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-aliveこれに対し、サーバは以下のようなレスポンスをクライアントに返す。
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 137
Date: Mon, 23 Mar 2026 04:00:00 GMT
Server: nginx
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
Hello World
</body>
</html>レスポンスは、以下の3つで構成される。
- ステータスライン(
HTTP/1.1 200 OK) - ヘッダ
- ボディ(HTMLなどの本体データ)
ブラウザがこれを読み取り、レンダリングが行われる。
HTTPリクエストメソッド
GET:指定したリソースの表現をリクエストする。データの取得のみ。HEAD:レスポンス本文なし、ヘッダのみのGETPOST:指定したリソースに実体を送信するPUT:対象リソースの現在の表現全体を、リクエストのコンテンツで置き換えるDELETE:指定したリソースを削除
HTTPレスポンスのステータスコード
- 情報レスポンス (100 – 199)
- 成功レスポンス (200 – 299)
- リダイレクトメッセージ (300 – 399)
- クライアントエラーレスポンス (400 – 499)
- サーバーエラーレスポンス (500 – 599)
ex.)
- 200 OK:リクエスト成功
- 404 Not Found:リクエストされたリソースを発見できない
- 500 Internal Server Error:サーバ側で処理エラー
1-3. 認証とセッション管理
認証とは
ユーザなどのエンティティが主張する身元を検証するプロセス。
なりすまし・不正アクセスを防ぐ。
認証要素
- 知識要素(パスワード、秘密の質問)
- 所持要素(電話番号、認証アプリのPINコード)
- 生体要素(指紋、顔、虹彩)
これらを2種類以上組み合わせたものを、多要素認証(MFA)という。
認証と認可
- 認証:あなたは誰?(Authentication)
- 認可:何をして良いか?(Authorization)
case1. 分離させる
ログイン時 → ID/パスワードで認証する
ログイン後 → 各API(
/adminや/api/paymentなど)アクセス時に認可を行う
case2. 同時に行う
ログイン時にロールを付与
permissions: ["read", "write"]などをセッションやJWT(後述)に含める
Cookieによるセッション管理
TIP
セッション = ログインしてからログアウトするまでの、一連の通信のまとまり
HTTPはステートレスなプロトコル。リクエストとレスポンスの1往復で完結する仕様のため、前後の通信から情報を引き継ぐことはできない。
ステートレスの欠点 → ログイン状態を保持できない!
Webサーバとクライアント間の状態を管理する、Cookieという仕組みが生まれた。
1994年、ネットスケープコミュニケーションズ社のエンジニアであるルー・モントゥリ(Lou Montulli)氏が、ユーザーの訪問情報を識別する目的で発明した。
Cookieでは、Webサーバが保存しておきたい情報を生成し、HTTPヘッダを使ってブラウザ(クライアント)に送信する。ブラウザはこれを保存し、以降の通信で、必要に応じてサーバに送信する。
ログイン認証の2種類
セッションベース(↑のセッションとは違う意味)
ステートフル=サーバが認証情報(セッション)を保存する。
サーバが、入場者リストを持っている。
トークンベース
ステートレス=クライアントがトークンを保存する。
各クライアントが、署名つきで偽造できない入場券を持っている。
セッションベース認証
認証時、サーバがセッションIDを発行し、サーバ側、クライアント側の双方でセッション情報を保存し、以降のリクエストではそれを照合する。
サーバ側:セッションストア(∴ ステートフル)
クライアント側:ブラウザのCookie

認証済みのリクエストは、次のように処理される。

サーバ側での、セッションIDのタイムアウトには、2種類の方式がある。
アイドルタイムアウト
一定時間操作がなかったらログアウトする。
絶対タイムアウト
「ログイン後 xxx時間後に強制ログアウト」がAPロジックで定義されている。
同時に、クライアント側でもCookieのタイムアウトが起こる。
Set-Cookie: session_id=abc123; Max-Age=3600(このCookieを何秒間保存するか)
Set-Cookie: session_id=abc; Expires=Wed, 09 Jun 2026 10:18:14 GMT
サーバが、これらをレスポンスヘッダに含め、「このCookieを保存してね」とクライアントに命令する。
WARNING
ブラウザにCookieが残っていても、サーバ側でセッションが切れていたら、再ログインが必要。
トークンベース認証
ログイン後、サーバはトークンをクライアントに発行し、以降のリクエストでは、それを送って本人確認する。
サーバ側:認証情報を持たない(∴ ステートレス)
クライアント側:ブラウザの localStorage や Cookie
最も有名なトークンの形式は、JWT(JSON Web Token)。


CSRFトークン
CSRF(Cross-Site Request Forgeries) 攻撃に対するセキュリティ対策。
脆弱性
罠サイトからのリクエストでも、ログインユーザーのセッションIDが付いていれば、本人からのリクエストだと見なされてしまう(なりすまし)。
成立条件
- ユーザーがログイン中であること
- Cookie認証を使っていること
ex.)
被害者がAmazonにログインした状態で、Amazonで100億円の買い物をするPOSTリクエストが自動送信される罠サイトを開いてしまう
これを悪用し、セッション固定攻撃(ログイン状態を固定する)、強制送金、アカウント乗っ取りなどが可能。
問題の原因
POST送信時、ブラウザが自動的に、リクエストにCookieを含めること。
解決策
/loginでサーバがランダムなトークン(文字列)を生成し、セッションに保存HTMLのフォーム内にも埋め込む
html<form action="/transfer" method="POST"> <input type="hidden" name="csrf_token" value="X8sk3Kdf" /> </form>POST送信時、そのトークンも一緒に送信される
サーバで照合
攻撃者は正しいCSRFトークンを取得できないため、不正なリクエストは403 Forbiddenで拒否される(理由は次ページ)。
同一オリジンポリシー(Same-Origin Policy)
1990年代後半、ブラウザにJavaScriptが導入されたことで生まれた仕様。JSは、同じオリジンのデータしか読めない。
TIP
オリジン = スキーム(プロトコル)+ ホスト + ポート
https://example.com/aのURLに対し、https://example.com/bは同一オリジンだが、https://example.com:8080やhttps://test.comは異なるオリジン。
罠サイトから正規サイトのHTMLを読むことはできないので、攻撃者は
POST /login
csrf_token=?????のリクエストを作ることはできない。
後に、SOPを部分的に解除するCORS(Cross-Origin Resource Sharing)も生まれた。
2. PHPの文法
2-1. オブジェクト指向
3大要素
カプセル化
データと処理を一つにまとめ、内部の詳細を外部から隠蔽する
インヘリタンス(継承)
スーパークラス(親)で定義した属性やメソッドをサブクラス(子)が使える
ポリモーフィズム(多態性)
同じ操作で、オブジェクトごとに結果が異なる
クラスとインスタンス
類似オブジェクトに共通する性質を抜き出し、属性(変数)やメソッド(関数)を一般化して、クラスを定義する。
それを使用して生成した実体(具体値を持ったオブジェクト)がインスタンス。
PHP, Java, C#はクラスベース ↔︎ JavaScriptはプロトタイプベース
2-2. 基本
変数は
$変数名で表記文字列は
.で連結する(echo "Hello " . $name;)echo:出力する::・・・クラス自身(インスタンス化する前)の静的メンバーにアクセスする->・・・オブジェクト実体(インスタンス)のプロパティやメソッドにアクセスする=>・・・配列のkeyとvalueを結びつけるex.) 連想配列(辞書)
$users = ["name" => "xxx", ...]ループ処理
foreach (<配列名> as $value) { 処理 }(インデックスも取る)
foreach (<配列名> as $key => $value) { 処理 }関数の宣言
function <関数名>(引数): 戻り値の型 { 処理 }三項演算子
条件 ? trueのとき : falseのときex.)
echo ($score >= 60) ? "pass" : "fail";===:型も値も比較(これを使う)==:型変換してから比較(0 == "apple"→true)__XXX__マジック定数/メソッド(
__FILE__,__DIR__,__LINE__, ...)exit(),die():プログラムを終了し、それ以降は実行しない
ファイル読み込み系
require <ファイル名>;:読み込んだファイルのPHPコードを実行するrequire_once <ファイル名>;:同じファイルを複数回読み込まないようにするfile_get_contents(<ファイル名>):ファイルの中身を文字列として取得する
クラス関連
$xxx = new <クラス名>(引数);でインスタンスオブジェクトを生成引数は、クラスのコンストラクタ(オブジェクト生成時に自動実行されるメソッド、
__constructと表記)に渡るクラスのアクセスを制御する修飾子(変数・メソッドの前につける)
修飾子 同じクラス 子クラス 外部 public○ ○ ○ protected○ ○ × private○ × × データ(変数)はprivate、操作(メソッド)はpublicにする(カプセル化)。
2-3. Web開発で頻出
null合体演算子
??($a ?? $b;aがnullならb)null合体代入
??=($a ??= $b;aがnullのときだけbを代入)superglobals(すべてのスコープで使用できる変数)
変数 意味 $_GETURLパラメータ $_POSTPOSTデータ $_SESSIONセッション $_COOKIEクッキー $_SERVERサーバ情報 empty($a)aが空なら真(バリデーションで使用)filter_input(<リクエストメソッド名>, <キー>, <変換法>)HTTPリクエストから値を取得するheader(<リクエストに含めるヘッダ>)生のHTTPヘッダを送信するex.) header関数でリダイレクトする例
phpheader("Location: ./test2.php"); // test2.phpへリダイレクト exit();session_start();セッション管理(session_idの生成・Cookieおよびセッションストアに保存)を実行する
3. 実装
PHP / HTML / CSS で簡易的なログイン機能を実装する。
リポジトリのクローン
$ git clone https://github.com/74rina/Login.gitアプリケーションのビルド
$ php init_db.php(DBのテーブルを作成・シードデータを投入)$ php -S localhost:8000(PHPのwebサーバを起動)
(Dockerを使う場合・・・2は行わず、$ docker compose up --build)
ブラウザで、
http://localhost:8000/login.phpにアクセスuser@example.com/password123でログイン
ディレクトリ構造
login
├── auth.php(マイページ上のガード)
├── config.php(DBの接続設定)
├── data
│ └── app.sqlite
├── init_db.php(テーブルの作成)
├── login_process.php(ログイン処理)
├── login.php
├── logout.php(ログアウト処理)
├── mypage.php
├── session.php(セッションIDの生成)
└── style.css3-1. データベースの設定
DB接続を設定する
config.phpを作成- DBに接続するための関数
getPdo()
- DBに接続するための関数
DBを初期化する
init.phpを作成- users テーブルの作成
- シードデータの投入
すでにDBがあれば実行しないので、例外処理の try-catch文 で実装する。
phptry { 危険そうな処理 } catch (Exception $e) { エラー時の処理 }
PDOクラス
PHPでDBに接続+操作する際は、共通インタフェースであるPDO(PHP Data Objects)クラスを用いる。
インスタンス$pdoを生成し、そのメソッドでDBを操作する。
$pdo = new PDO(
"mysql:host=localhost;dbname=test", // 接続先
"user", // DBユーザ
"password" // DBパスワード
);第一引数のDSN(Data Source Name)は、次のように環境変数(後述)から値を取り出して書くことが多い。
$dsn = "mysql:host=".$_ENV['DB_HOST'].";dbname=".$_ENV['DB_NAME'];PDOクラスのメソッドは以下。
| メソッド | 役割 | 例 |
|---|---|---|
__construct() | DB接続を作る | $pdo = new PDO($dsn,$user,$pass); |
prepare() | SQLを準備(プリペアドステートメント) | $stmt = $pdo->prepare($sql); |
query() | SQLを直接実行(SELECTなど) | $pdo->query("SELECT * FROM users"); |
exec() | SQL実行(INSERT/UPDATE/DELETE) | $pdo->exec($sql); |
beginTransaction() | トランザクション開始 | $pdo->beginTransaction(); |
commit() | トランザクション確定 | $pdo->commit(); |
rollBack() | トランザクション取り消し | $pdo->rollBack(); |
lastInsertId() | 最後にINSERTしたID取得 | $pdo->lastInsertId(); |
setAttribute() | PDO設定変更 | $pdo->setAttribute(...); |
getAttribute() | PDO設定取得 | $pdo->getAttribute(...); |
errorCode() | エラーコード取得 | $pdo->errorCode(); |
errorInfo() | エラー詳細取得 | $pdo->errorInfo(); |
quote() | SQL用に文字列をエスケープ | $pdo->quote($str); |
プリペアドステートメント
$query = "SELECT * FROM users WHERE name = '$name'";でユーザ入力$nameを受け取る際、正規のユーザ名ではなく、' OR 1=1 --が入力されると、
SELECT * FROM users WHERE name = '' OR 1=1 --'で全ユーザが取得されてしまう(∵ 1=1は常に真)。→ SQLインジェクション
これを防ぐために、?プレースホルダを用いて、SQL実行とデータ代入を分離する。
$stmt = $pdo->prepare(
"SELECT * FROM users WHERE name = ?"
);
$stmt->execute([$name]);環境変数
秘密情報(パスワードなど)は、プログラムのコードに直接書く(=ハードコーディング)ことはせず、環境変数として別ファイル(.env)に書く。
.envファイルの例(開発用)
DB_HOST=localhost
DB_USER=admin
SECRET_KEY=abc123
PORT=3000このファイルは、GitHub上で公開しない(.gitignoreで設定)。チーム開発では、別の安全な方法で共有する。
3-2. ログインページ・マイページの作成
「login.phpでログイン成功 → mypage.phpに遷移」というフローにする。実際のログイン処理(値の照合・認証など)は切り分ける。
今回は、PHPの内部にHTMLを書き、フロントエンドを構築する。
PHP部分は <?php と ?> で挟む
Webサーバがブラウザからのリクエストを受信し、APサーバに転送後、
APサーバ(PHP)は、タグ<?php ... ?>を見つけて中身を実行し、それ以外のHTML/CSSはそのまま出力する。
ブラウザは、受け取った文字列をHTMLとして解釈し、レンダリングする。
3-3. ログイン処理
実際のログイン処理をlogin_process.phpで実装する。
ログインページを踏んだら、まずCSRFトークンを生成
CSRFトークンが合っているか検証
php// login_process.php $token = $_POST['csrf_token'] ?? ''; if (!hash_equals($_SESSION['csrf_token'], $token)) { ... }バリデーション(=入力の空文字チェック)
DBから値を取得し、ユーザ存在確認+パスワードの検証
↑を突破したら、①セッションIDの作成 ②ユーザ情報をセッションに保存
CSRFトークンは、session.phpを一回実行することで生成される。
// session.php
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));未ログイン状態でマイページにアクセスしたときのガードは、auth.phpに実装する。
// auth.php
function requireLogin(): void
{
if (empty($_SESSION['user_id'])) {
$_SESSION['flash_error'] = 'ログインが必要です。';
header('Location: login.php');
exit;
}
}ハッシュ化
特定の計算方法に基づき、元データをハッシュ値(不規則な固定長文字列)に置換し、データを保護すること(≠ 暗号化)。
ハッシュ関数:任意の入力データをハッシュ値に変換する関数(以下の特徴をもつ)。
- 固定長の出力(ex. SHA-256 = 256ビットのハッシュ値を生成するハッシュ関数)
- 一方向性(ハッシュ値から元のデータを復元できない)
- Avalanche効果(入力が少し変わるだけで出力が大きく異なる)
レインボーテーブル:平文とハッシュ値の対応表(ソルトで無効化)
ソルト:ハッシュ化する前に、平文に足すランダムな文字列
レインボーテーブル攻撃:攻撃者が大量のパスワードをハッシュ化し、同じハッシュ値がレインボーテーブルに存在した場合に、その平文パスワードが盗まれる。
Webの認証でも、ハッシュ化が用いられる。PHPでは以下の関数を使用する。
パスワード保存時 平文パスワードをハッシュ化し、ソルトも自動生成
php// init_db.php $hash = password_hash($password, PASSWORD_DEFAULT);パスワード検証時 内部で
bcryptなどのアルゴリズムが用いられているphp// login_process.php if (!$user || !password_verify($password, $user['password_hash'])) { ... }
4. 演習
ログアウト処理の実装を考えてみましょう!
実装のヒント
ログアウトとは? → セッションIDを無効化すること。
実装例
// logout.php
<?php
require_once __DIR__ . '/session.php';
// セッション変数を空にする
$_SESSION = [];
// セッションクッキーも無効化する
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();
// ログアウト後はログイン画面へ
session_start();
$_SESSION['flash_success'] = 'ログアウトしました。';
header('Location: login.php');
exit;マイページのmain内にログアウトボタンを追加
// mypage.php
...
<div class="actions">
<a class="btn-link" href="logout.php">ログアウト</a>
</div>
...お疲れさまでした!
実際の開発では、認証やセッション管理などの仕組みはWebフレームワークが提供してくれるため、これらを一から設計することはありません。
しかし、ログイン処理の「裏側の仕組み」を理解しておくことが、安全なWebサイトを設計する(DevSecOps)ための基礎となると思います。
今回登場した「データベース」については、次回の 入門2 で深めていきます!
参考文献
- Qiita JWT認証の流れを理解する