ログイン機能の仕組み — 認証・セッション・Cookie

この記事は、プログラミングやバイブコーディングをする上で最低限身につけたほうがよい IT の実践的な知識を 1 から身につける「IT 基礎知識講座の一部」です。
ログイン機能は、本人かを確かめる認証と、そのあとも同じ人と分かるセッションでできています。ハッシュと Cookie の役割を図解で確かめます。

この記事は、ログイン機能の仕組みを扱います。

ログイン機能は、本人かを確かめる仕組みと、そのあとも同じ人だと分かる仕組みの 2 つでできています。

「ハッシュ化」「セッション方式」「JWT」は、1 つのログイン機能を場面ごとに呼び分けたものです。

ログイン機能は、2 つの仕組みでできている
tanaka さんがログインする本人かを確かめるそのあとも同じ人だと分かるパスワードのハッシュを比べるCookie のセッション ID
左の 1 回のログインから上下に分かれます。上は最初の 1 回だけ行う確認、下はそのあとのリクエストで毎回行う確認です。右がこの記事で扱う用語。

上の枝が最初の 1 回だけ行う確認、下の枝がそのあと毎回行う確認です。

認証は本人かを確かめること、認可は何をしてよいかを決めること

認証(authentication。利用者が本人であることを確かめること)は、ログインのときに行う判断です。

認可(authorization。確かめた利用者に、何をしてよいかを決めること)は、そのあとに行われる別の判断です。

予約を削除するリクエスト 1 回の中で、サーバーが行う 2 つの判断
「この予約を削除する」というリクエスト 1 回
1 つ目の判断 — 認証(tanaka さん本人か)
  • もとにするもの — ID とパスワード、ログイン後は Cookie のセッション ID
  • 通らないときの応答 — ログイン画面に戻す
  • パスワードで確かめるのは 1 回、そのあとはセッション ID
2 つ目の判断 — 認可(この予約を消してよいか)
  • もとにするもの — 予約の持ち主が誰か、利用者が管理者か
  • 通らないときの応答 — 権限がありませんと返す
  • 操作するたびに、毎回確かめる
外側が 1 回のリクエスト。中に 2 つの判断が上から順に入っている。上を通っても、下で断られることがある。

同じ tanaka さんの操作でも、2 つは別々に判定されます。

同じ tanaka さんでも、認証と認可のどちらで止まるかが変わる
ログインせずに予約一覧を開くログインして自分の予約を削除ログインして他人の予約を削除認証を通らない認証を通る認証を通る判断まで進まない持ち主が本人→ 通る持ち主が別人→ 通らないログイン画面削除しました権限がありません
各行が 1 回の操作です。左から順に認証、認可を通り、通らなかった時点で右の画面が返ります。いちばん上は認可まで進みません。

認証で止まる行と、認証を通ったあとに認可で止まる行があります。

設定の「管理者だけが使える」「自分のデータしか見えない」は、この認可の話です。

本人かを確かめるのが認証、してよいかを決めるのが認可

ログインできることと、その操作をしてよいことは、別々に決まります。

本人かどうかを確かめるのが認証、その人がその操作をしてよいかを決めるのが認可で、認可は操作するたびにサーバー側で確かめます。

パスワードはハッシュにして保存し、ハッシュ同士を比べる

認証でまず押さえるのは、サーバーがパスワードそのものを保存していないことです。

保存されているのはハッシュ(hash。元の文字列から決まった手順で作った、元には戻せない文字列)です。

打ち込んだ文字その場でハッシュ化保存済みのハッシュと比べる判定
hanabi2026(正しい)a3f9...c1a3f9...c1 と同じログイン成功
hanabi2025(1 文字違い)7b20...e4a3f9...c1 と違うログイン失敗
Hanabi2026(大文字違い)d15c...8aa3f9...c1 と違うログイン失敗

1 文字違うだけで、できあがるハッシュはまったく別のものになります。

元に戻せなくても確かめられるのは、比べる相手もハッシュだからです。

サーバーが持っているのは、戻せない文字列だけです。

データベースに保存されているものと、されていないもの
my-app のデータベース
users テーブルの 1 行(tanaka さん)
  • email — tanaka@example.com
  • password_hash — a3f9...c1
  • created_at — 会員登録した日時
この行に入っていないもの
  • hanabi2026 という打ち込んだ文字
  • 打ち込んだ文字に戻せる形のデータ
  • 今ログイン中かどうかの状態
my-app のデータベースの中。tanaka さんの 1 行に入っているのは、メールアドレスとハッシュと登録日時だけ。

ハッシュで保存するのは、データベースの中身が漏れたときに備えるためです。

よく使われる文字列は当てられるので、ハッシュ化の前に利用者ごとに違う文字列(ソルト)を足します

送るときの HTTPS は途中で読まれないための対策で、保存の形とは別の話です。

ハッシュが一致したかどうかで、次に起きることが分かれる
tanaka さんがログインを押すハッシュを比べる一致した違った記録を作りセッション ID を返すログイン画面をもう一度返す
左の 1 回のログインが、比べた結果で上下に分かれる。一致したときだけ、サーバーの中に記録が作られる。

保存してあるのはハッシュで、比べる相手もハッシュ

サーバーは、利用者が打ち込んだパスワードを持っていません。

持っているのは元には戻せない文字列だけで、ログインのたびに打ち込まれた文字を同じ手順にかけ、できた文字列が保存してあるものと同じかどうかを見ています。

ログインしたあとは、Cookie のセッション ID でサーバーが同じ人と分かる

ログインに成功しても、次のページを開くリクエストは、別のリクエストです。

HTTP はステートレス(1 回の処理が、前の処理を覚えていないこと)なので、直前に誰がログインしたかをサーバーは持っていません。

ログインが通ったことをサーバー側に記録し、その記録を指す文字列をブラウザが預かります。

サーバー側に残すこの記録がセッション(session)、それを指す文字列がセッション ID(session ID)、その文字列をブラウザに預けて毎回送らせる仕組みがCookie(cookie)です。

ログインしたあと、ブラウザとサーバーがそれぞれ持っているもの
ログインしたあとの my-app
tanaka さんのブラウザの中
  • Cookie: session_id=3f9a1c...e81b
  • 同じサイトへのリクエストに毎回自動で付く
  • この文字列のほかは何も持っていない
サーバーの中
  • セッションの記録 3f9a1c...e81b は tanaka さん
  • この記録に有効期限が付いている
  • ログアウトすると、この記録を消す
同じ 3f9a1c...e81b という文字列が、ブラウザ側とサーバー側の記録を結び付けている。名前も権限もサーバー側にしかない。

ブラウザ側にあるのは 3f9a1c...e81b という文字列だけで、名前も権限も入っていません。

誰のログインかが分かるのは、サーバー側の記録と結び付いたときだけです。

上の行だけが記録と結び付くので、パスワードを聞き直さずに予約一覧が返ります。

この受け渡しは、次の 2 行の形で行われています。

# ログインに成功したとき、サーバーが返す行
Set-Cookie: session_id=3f9a1c...e81b; HttpOnly; Secure

# 以後、ブラウザが同じサイトへ送るリクエストに毎回付く行
Cookie: session_id=3f9a1c...e81b

Set-Cookie の後ろの 2 つは、ブラウザへの指示です。

HttpOnly は画面の JavaScript からこの Cookie を読めなくし、Secure は HTTPS のときだけ送らせます。

Cookie の中身は利用者が書き換えられるので、そのまま信じるかどうかで結果が変わります。

Cookie の値をそのまま信じるかどうかで、結果が変わる
is_admin=true(管理者だという値)を足して送るCookie の値をそのまま信じる管理者の画面が出るsession_id から記録を読む一般の利用者のまま
利用者が書き換えた Cookie が届いたとき。上の道は届いた値で判断し、下の道はサーバーの記録で判断する。

Cookie はブラウザに保存されるので、利用者が中身を書き換えて送れます。

Cookie に入れるのはセッション ID だけにし、判断はサーバー側の記録で行います。

左の囲みに置かれるのは、意味を持たない文字列 1 つだけです。

誰であるか、何をしてよいかを決める材料は、すべて右の囲みの中にあります。

ブラウザが預かるのは、記録を指す文字列 1 つだけ

サーバーは、リクエストが届いた時点では、それが誰からのものか分かりません。

分かるのはログインしたときに作った記録とブラウザが毎回送る文字列が結び付いているからで、記録が消えるか期限が切れれば、同じ文字列を送ってもログイン画面に戻ります。

セッション方式と JWT の違い — 記録をサーバーが持つか、文字列が持つか

「セッション方式か JWT か」は、ログイン済みの証明をどちらが持つかの選び方です。

もう 1 つは、サーバーに記録を残さず、証明そのものをブラウザに持たせる方式です。

毎回付けて送るこの証明の文字列がトークン(token)で、JWT(JSON Web Token。利用者の ID と有効期限と署名をまとめた文字列)が代表的な形式です。

ブラウザが預かる JWT 1 つの中身
ブラウザが毎回付けて送る 1 つの文字列(JWT)
中身 — 誰のものか・いつまで有効か
  • tanaka さんの利用者 ID
  • 有効期限(この時刻まで使える)
署名 — サーバーだけが作れる値
  • 中身から計算した文字列
  • 中身を書き換えると計算が合わなくなる
外枠が毎回送られる 1 つの文字列。中身は読めば分かる形で入っていて、書き換えを見つけるのが署名です。

署名があるので、サーバーは記録と照合しなくても、届いた文字列が自分の発行したものだと分かります。

観点セッション方式JWT
ログイン成功時に渡すものsession_id=3f9a1c...e81beyJhbGci... ID と期限と署名
判断のもとがある場所サーバーの中の記録文字列の中の中身
毎回の確かめ方記録と照合する署名を検証する

JWT は中身を持っているので、サーバーの記録がなくても受け入れられます。

セッション方式は記録を消せば、次のリクエストから通らなくなります。

JWT は期限が来るまで通るので、すぐ止めたいときは無効にしたものの一覧を別に持ちます。

この記事では、セッション方式は Cookie、JWT は文字列として分けて描きましたが、実際には JWT を Cookie に入れる構成もあります。

置き場所が変わっても、記録と照合するか署名を確かめるかという違いは同じです。

記録をサーバーが持つか、文字列が中身ごと持つか

どちらの方式でも、ブラウザが毎回 1 つの文字列を付けて送るところは同じです。

違うのは受け取ったサーバーが何をするかで、セッション方式は自分の記録を見に行き、JWT は文字列に付いた署名を確かめます。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1サーバーが利用者のパスワードを保存するとき、データベースに置かれているものはどれですか。

Q2ログイン後に別のページを開いたとき、サーバーが同じ利用者だと分かるのはなぜですか。

Q3ログイン済みの利用者が他人の予約を削除しようとして、サーバーに断られました。この判断はどれですか。