はじめに
自作のWebアプリでログイン機能を作ろうとすると、「セッションCookie」「JWT」「パスワードのハッシュ化」といった用語に必ず出会います。それぞれ役割が異なるため、まとめて整理しておきます。
前提:HTTPはステートレス
HTTPというプロトコルは、本来リクエストごとに独立していて、前回のやり取りを覚えていないステートレス(状態を持たない) な仕組みです。そのため、「ログインした状態を維持する」ためには、何らかの方法でサーバーとクライアントの間に「この人はログイン済みだ」という状態を持たせる工夫が必要になります。この工夫の代表例が、これから説明するセッションCookie認証とJWT認証です。
セッションCookie認証
仕組み
- ユーザーがメールアドレス・パスワードでログインする
- サーバーはログイン成功時、サーバー側のメモリやDBに「セッション情報(誰がログイン中か)」を保存し、それを参照するためのセッションIDを発行する
- サーバーは、このセッションIDをCookieとしてブラウザに送る
- ブラウザは以降のリクエストで自動的にこのCookieを送り返す
- サーバーは受け取ったセッションIDを使って、保存しておいたセッション情報を照合し、ログイン状態を判定する
Cookie自体は、サーバーがブラウザに保存させる小さなデータのことで、Set-Cookieヘッダーで送信されます。ブラウザは以後、同じサーバーへのリクエストに自動的にそのCookieを添付します。
セキュリティ上重要な属性
| 属性 | 内容 |
|---|---|
Secure |
https通信の場合のみCookieを送信する。httpでは送信されない |
HttpOnly |
JavaScriptからCookieの値を読み取れなくする。XSS(クロスサイトスクリプティング)対策として重要 |
SameSite |
他サイトからのリクエストにCookieを添付するかどうかを制御し、CSRF対策に寄与する |
Secure属性が付いたCookieは、https化されていないサイトでは保存も送信もされません。開発環境ではhttpのまま気づかず動いていたログイン機能が、本番のhttps化前のドメインにアクセスした途端に機能しなくなる、というのはこの属性が原因で起きる典型的な現象です。
JWT(JSON Web Token)認証
仕組み
JWT(JSON Web Token) は、RFC 7519で定義された、情報をコンパクトかつ安全に受け渡すためのトークン形式です。セッションCookie方式との最大の違いは、サーバー側にセッション情報を保存しないという点にあります。
- ユーザーがログインすると、サーバーはユーザーIDなどの情報(クレーム)を含んだJWTを生成し、署名を付けてクライアントに返す
- クライアントはこのJWTを保持し、以降のリクエストのヘッダー(
Authorization: Bearer <token>)等に載せて送信する - サーバーは、受け取ったJWTの署名を検証するだけでその中身(誰がログイン中か)を信頼でき、DBやメモリへの照会が不要
JWTは.(ドット)で区切られた3つの部分から構成されます。
ヘッダー.ペイロード.署名
- ヘッダー:署名アルゴリズムなどのメタ情報
- ペイロード:ユーザーIDなど実際のデータ(クレーム)。Base64エンコードされているだけで暗号化はされていないため、機密情報を含めるべきではない
- 署名:秘密鍵(またはRSA/ECDSAの鍵ペア)を使って生成される、改ざん検知のための署名
サーバー側は署名を検証することで「このトークンは自分が発行したものであり、改ざんされていない」ことを確認できますが、一度発行したトークンをサーバー側の意思で即座に無効化することは、セッションCookie方式に比べて難しいという特徴があります(有効期限を短く設定する、失効リストを別途管理する等の工夫が必要)。
セッションCookieとJWTの比較
| 項目 | セッションCookie | JWT |
|---|---|---|
| ログイン状態の保存場所 | サーバー側(DB・メモリ等) | クライアント側(トークン自体に情報を含む) |
| サーバーの状態管理 | 必要(ステートフル) | 基本的に不要(ステートレス) |
| 即時ログアウト・強制失効 | サーバー側で容易に無効化できる | 単純な実装では困難(有効期限や失効リストの管理が必要) |
| 複数サーバーでのスケール | セッション情報の共有が必要になる場合がある | トークン検証だけで完結しやすく水平スケールしやすい |
| 主な用途 | 従来型のWebアプリケーション | API・SPA・マイクロサービス間の認証 |
パスワードのハッシュ化
ログイン機能を作る上で絶対に避けなければならないのが、パスワードを平文のままDBに保存することです。万が一DBが漏洩した場合、利用者がほかのサービスでも使い回している可能性のあるパスワードがそのまま流出してしまいます。
ハッシュ化とは
ハッシュ化は、入力値を一定長の不可逆な(元に戻せない)文字列に変換する処理です。パスワードそのものではなく、ハッシュ化した値をDBに保存し、ログイン時には入力されたパスワードを同じ方式でハッシュ化して、保存された値と一致するかどうかを比較します。
単純なハッシュ関数(SHA-256等)をそのままパスワードに使うのは推奨されません。計算が高速すぎるため、攻撃者が大量の候補を試す総当たり攻撃(ブルートフォース)に弱いためです。そのため、パスワード用には意図的に計算コストを高くした専用のハッシュアルゴリズムを使います。
代表的なアルゴリズム
| アルゴリズム | 特徴 |
|---|---|
| bcrypt | 長年使われてきた実績のあるアルゴリズム。ソルト(後述)が自動的に組み込まれる |
| Argon2(Argon2id) | Password Hashing Competitionの優勝アルゴリズム。OWASPが現在最も推奨 |
| scrypt / PBKDF2 | bcryptと同様に計算コストを調整できるアルゴリズム |
OWASP(Open Worldwide Application Security Project)のパスワード保存に関するガイドラインでは、モダンなアプリケーションにはArgon2idを第一候補として推奨しつつ、bcryptもレガシー環境での選択肢として認めています。
ソルトの役割
同じパスワードを使う複数のユーザーがいた場合、単純にハッシュ化しただけでは同じハッシュ値になってしまい、あらかじめ計算しておいたハッシュ値の対応表(レインボーテーブル)を使った攻撃に弱くなります。ソルトは、ハッシュ化の際にユーザーごとにランダムな値を追加する仕組みで、同じパスワードでも異なるハッシュ値になるようにします。bcryptやArgon2のような専用アルゴリズムは、このソルトの生成・管理を自動的に行ってくれます。
まとめ
| 用語 | 内容 |
|---|---|
| セッションCookie認証 | サーバー側にログイン状態を保存し、CookieでセッションIDをやり取りする方式 |
| JWT | サーバー側の状態管理なしに、署名付きトークン自体で認証情報を伝える方式 |
| Secure/HttpOnly/SameSite | 認証Cookieを守るための重要な属性 |
| パスワードハッシュ化 | 平文パスワードを保存せず、不可逆な変換をした値を保存する仕組み |
| ソルト | 同じパスワードでも異なるハッシュ値になるよう加えるランダムな値 |