😶🌫️
OAuth・JWT・CSRF理解ガイド - 初心者エンジニア向け
はじめに
この記事では、Web開発で必ず出会うOAuth、JWT、CSRF対策について、初心者でも理解できるようになるべく丁寧に解説します。
「Google認証って何?」「JWTって暗号化されてるの?」「CSRFトークンって必要?」といった疑問を、実際の動きを追いながら解決していきます。
目次
OAuthとは?Google認証との関係
OAuthの基本概念
OAuth 2.0は、「パスワードを教えずに、他のアプリに自分のデータへのアクセスを許可する仕組み」です。
登場人物
- リソースオーナー = あなた(Googleアカウントを持つユーザー)
- クライアント = 使いたいアプリ(例:スケジュール管理アプリ)
- 認可サーバー = Googleの認証システム
- リソースサーバー = GoogleのAPI(Gmail、カレンダー、スプレッドシートなど)
Google認証の実際の流れ
【あるアプリがGoogleカレンダーにアクセスしたい場合】
1. アプリ:「Googleでログイン」ボタンを表示
↓
2. ユーザー:ボタンをクリック
↓
3. ブラウザ:Googleのログイン画面へ遷移(認可サーバー)
↓
4. Google:「このアプリがカレンダーにアクセスすることを許可しますか?」
↓
5. ユーザー:「許可する」をクリック
↓
6. Google:アプリにアクセストークンを発行
↓
7. アプリ:トークンを使ってGoogleカレンダーAPIを呼び出し
メリット
✅ アプリにGoogleのパスワードを教えなくていい
✅ 「カレンダーだけ」「読み取りだけ」など権限を限定できる
✅ 後からいつでも許可を取り消せる
アクセストークンの仕組み
よくある疑問
「アクセストークンを認可サーバーからもらってリソースAPIにアクセスするのは分かるけど、リソースサーバーはどうやってこのトークンが誰のものか判断するの?」
答え1:認可サーバーに問い合わせる
クライアント → リソースサーバー:
「トークンABCでカレンダー取得して」
↓
リソースサーバー → 認可サーバー:
「トークンABCって誰の?有効?」(トークンイントロスペクション)
↓
認可サーバー → リソースサーバー:
「ユーザー12345、有効、権限OK」
↓
リソースサーバー:
ユーザー12345のカレンダーを返す
答え2:JWT(自己完結型トークン)
実はGoogleなど多くのサービスはJWT(JSON Web Token)を使っていて、これだと問い合わせ不要です。
JWTの構造
// JWTの中身(Base64デコードすると見える)
{
"user_id": "12345",
"email": "user@example.com",
"scope": "https://www.googleapis.com/auth/calendar.readonly",
"exp": 1699999999 // 有効期限
}
// + 認可サーバーの秘密鍵による署名
処理の流れ
1. リソースサーバーがJWTを受け取る
2. 認可サーバーの公開鍵で署名を検証(改ざんされていないか)
3. 有効期限をチェック
4. トークン内のuser_idを見てデータを返す
※ 認可サーバーへの問い合わせ不要!高速!
JWTとセキュリティ
よくある疑問
「公開鍵で誰でも検証できるなら、セキュリティ的に危なくない?」
答え:危なくありません
重要な区別
- 誰でも検証できる ≠ 誰でも偽造できる
❌ 攻撃者がやりたいこと
「user_id: 99999(他人のID)」という偽トークンを作る
→ 秘密鍵がないので署名を作れない
→ リソースサーバーで検証失敗
✅ 正規のトークン
認可サーバーが秘密鍵で署名
→ 誰でも公開鍵で「これは本物だ」と確認できる
→ でも中身は変更できない
トークンの中身を見られても大丈夫
JWTはBase64エンコードされているだけなので、デコードすれば誰でも中身を見られます。
// デコードすると...
{
"user_id": "12345",
"email": "user@example.com"
}
でも問題ない理由:
- ✅ 見られても問題ない情報しか入っていない
- ❌ パスワードは入っていない
- ❌ 個人的なデータは入っていない
本当のセキュリティ対策
JWTの署名ではなく、以下で守られています。
1. HTTPS通信
🔒 盗聴防止:通信内容が暗号化
🔒 なりすまし防止:接続先が本物のサーバーか確認
🔒 改ざん防止:途中でデータを書き換えられない
2. トークンの適切な管理
- 短い有効期限(1時間など)
- httpOnlyクッキーに保存
- リフレッシュトークンで更新
3. 二重の防御
HTTPS(TLS/SSL)
└─ 通信中の盗聴・改ざん防止
JWT の署名
└─ トークンの偽造・改ざん防止
CSRF対策の必要性
CSRF攻撃の例
<!-- 悪意のあるサイト evil.com -->
<form action="https://your-bank.com/transfer" method="POST">
<input name="to" value="攻撃者の口座">
<input name="amount" value="100万円">
</form>
<script>
document.forms[0].submit(); // 自動送信
</script>
何が起きるか
1. ユーザーが銀行サイトにログイン中
2. 悪意のあるサイト evil.com を開く
3. ブラウザが自動的にCookie(JWTやセッションID)を送信
4. サーバーは「認証済みユーザーだ」と判断
5. 送金が実行されてしまう ⚠️
問題点: JWTがCookieに入っていると、ブラウザが勝手に送ってしまう
使い分け
ケース1:SPA(React等)でJWT
// ✅ localStorage に保存 → CSRF対策不要
localStorage.setItem('token', jwt);
// リクエスト時に明示的にヘッダーに付ける
fetch('/api/transfer', {
headers: {
'Authorization': `Bearer ${jwt}` // ブラウザが勝手に送らない
}
});
ケース2:従来のフォーム送信(Laravel等)
<!-- セッションベース認証 + CSRFトークンが必須 -->
<form method="POST" action="/transfer">
@csrf
<input name="amount" value="1000">
<button>送金</button>
</form>
LaravelのCSRF対策の仕組み
@csrf の正体
<form method="POST" action="/transfer">
@csrf <!-- これが重要 -->
<input name="amount" value="1000">
<button>送金</button>
</form>
実際に生成されるHTML:
<form method="POST" action="/transfer">
<input type="hidden" name="_token" value="ランダムな文字列ABC123...">
<input name="amount" value="1000">
<button>送金</button>
</form>
動作の流れ
1. ページ表示時
サーバー側:
1. ランダムなCSRFトークンを生成(例: token_A)
2. セッションに保存
3. フォームに埋め込む
2. フォーム送信時
サーバー側:
1. セッションに保存されているトークン(token_A)を取得
2. フォームから送信されたトークン(_token)を取得
3. 両者を比較
4. ✅ 一致 → 処理続行
❌ 不一致 → 419エラー(CSRF token mismatch)
3. 攻撃が防げる理由
悪意のあるサイトからのリクエスト:
- セッションのトークンを読めない(同一オリジンポリシー)
- だから正しいトークンを送れない
- リクエストが拒否される ✅
セッションの管理
複数ユーザーの同時アクセス
重要: セッションはユーザーごとに別々に管理されます。
Aさんがログイン画面を開く:
1. サーバー:セッションID「abc123」を生成
2. サーバー:CSRFトークン「token_A」を生成
3. DB:セッションID abc123 に token_A を保存
4. レスポンス:
- Cookie: session_id=abc123
- HTML: <input value="token_A">
Bさんがログイン画面を開く(同時刻):
1. サーバー:セッションID「xyz789」を生成(別のID!)
2. サーバー:CSRFトークン「token_B」を生成
3. DB:セッションID xyz789 に token_B を保存
4. レスポンス:
- Cookie: session_id=xyz789
- HTML: <input value="token_B">
DBの状態
sessions テーブル
┌─────────────┬──────────────────────┬─────────────┐
│ session_id │ payload (暗号化) │ last_activity│
├─────────────┼──────────────────────┼─────────────┤
│ abc123 │ {csrf: "token_A"} │ 1699999999 │
│ xyz789 │ {csrf: "token_B"} │ 1699999999 │
└─────────────┴──────────────────────┴─────────────┘
検証の流れ
Aさんがログインボタンを押す
リクエスト:
- Cookie: session_id=abc123
- Body: _token=token_A, email=a@example.com, password=xxx
サーバー側:
1. Cookieから session_id=abc123 を取得
2. DBからセッション abc123 のデータを取得
3. セッション内のcsrf_token(token_A)と
フォームの_token(token_A)を比較
4. ✅ 一致 → ログイン処理続行
攻撃者が悪意のあるサイトから送信
リクエスト:
- Cookie: session_id=abc123(ブラウザが自動送信)
- Body: _token=攻撃者が適当に作った値
サーバー側:
1. Cookieから session_id=abc123 を取得
2. DBからセッション abc123 のデータを取得(token_A)
3. セッション内のtoken_Aと、フォームの偽トークンを比較
4. ❌ 不一致 → 419エラー(CSRF token mismatch)
セッションの保存場所
Laravelでは.envのSESSION_DRIVERで設定できます:
# ファイルに保存
SESSION_DRIVER=file
# データベースに保存
SESSION_DRIVER=database
# Redisに保存
SESSION_DRIVER=redis
# Cookieに暗号化して保存
SESSION_DRIVER=cookie
まとめ
OAuth・JWT・CSRFの関係
【OAuth】
└─ 認証・認可の仕組み全体
├─ 認可サーバー:トークン発行
└─ リソースサーバー:APIアクセス
【JWT】
└─ アクセストークンの一種
├─ 自己完結型(署名で検証)
└─ HTTPS + 署名で二重に保護
【CSRFトークン】
└─ フォーム送信の正当性を証明
├─ OAuthやJWTとは別物
└─ Cookieベース認証で必須
実務での注意点
OAuth/JWT実装時
- ✅ HTTPSは必須(開発環境でも)
- ✅ トークンの保存場所に注意
- ✅ 有効期限を短く、リフレッシュトークンで更新
CSRF対策
- ✅ Cookieベース認証では必須
- ✅ Laravelなら
@csrfを忘れずに - ✅ Authorization ヘッダーで送るJWTはCSRF対策不要
おわりに
OAuth、JWT、CSRF対策は、Web開発において必須の知識です。
エンジニアとしてのキャリアの第一歩として、セキュリティを意識した開発を心がけたいです!
引き続き、学習頑張ります🔥
Discussion