認証

更新日:

「誰が来たのかを確かめる仕組み」と、それを本番で安全に回し続ける運用のまとめ。攻撃手法ごとの対策は Webセキュリティ対策、フレームワーク別の実装は Next.js_08_認証とセキュリティ / TanStack Start_06_認証・DB・デプロイ・本番チェック を見る。

結論を先に書くと:

  • 自作しない。認証サービスか実績あるライブラリを使い、自分は「設定」と「認可」に集中する
  • ログイン状態は HttpOnly Cookie のセッションで持つのが基本
  • 外部ログインは OAuth 2.0 + OpenID Connect(認可コードフロー + PKCE)
  • これから作るなら パスキーを第一候補に、パスワードは補助
  • 一番事故が多いのは認証ではなく認可(他人のデータに触れてしまう)

用語の整理

用語意味例
認証(Authentication / AuthN)あなたは誰かログイン
認可(Authorization / AuthZ)あなたは何をしてよいか自分の注文だけ見られる、管理者だけ削除できる
セッションログイン状態をリクエストをまたいで覚えておくことCookie に入ったセッションID
IdP(Identity Provider)ログインを代行してくれる側Google、Apple、GitHub、Auth0
MFA(多要素認証)種類の違う要素を2つ以上組み合わせるパスワード + 認証アプリ

本人確認の3要素

  • 知識: 知っているもの(パスワード、PIN)
  • 所持: 持っているもの(スマホ、セキュリティキー、パスキー)
  • 生体: 本人の体(指紋、顔)

パスワード2つは「2段階」でも「2要素」ではない。違う種類を組み合わせて初めて多要素。

仕組み① ログイン状態の持ち方

HTTP は1回ごとに独立していて「さっきの人」を覚えていない(HTTP・HTTPS・TLS)。そこでログイン成功後に「通行証」を渡し、以降のリクエストで毎回見せてもらう。通行証の持ち方は大きく2つ。

セッション方式(サーバーが覚える)

1. ID/パスワードを送る
2. サーバーがDBにセッションを作り、ランダムなIDを Cookie で返す
   Set-Cookie: session=8f3a...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=...
3. 以降ブラウザが自動で Cookie を付けて送る → サーバーがDBで照合

トークン方式(トークン自体に情報を持たせる)

1. ログインすると、署名付きのトークン(JWT など)が返る
2. 以降 Authorization: Bearer <token> で送る
3. サーバーは署名を検証するだけ(DBを見なくてよい)
セッション(Cookie + DB)トークン(JWT)
状態サーバーに保存トークンに埋め込む(ステートレス)
即時ログアウト・強制失効簡単(DBから消すだけ)難しい(期限まで有効。失効リストが要る)
スケールセッションストアが必要検証だけなので軽い
向いている場面普通のWebアプリAPI、モバイルアプリ、マイクロサービス間

実務の判断: ブラウザ向けWebアプリならセッション Cookieで十分かつ安全。JWT は「失効が難しい」弱点があるので、使うなら下記の短命トークン + リフレッシュトークン構成にする。

JWT を使うときの注意

  • 中身は暗号化ではなく Base64。誰でも読める → 秘密情報を入れない
  • 署名アルゴリズムをサーバー側で固定する(alg: none や鍵の取り違え攻撃を防ぐ)
  • exp(期限)・iss(発行者)・aud(宛先)を必ず検証する
  • ブラウザの localStorage に置くと XSS で盗まれる → ブラウザなら HttpOnly Cookie に入れる

アクセストークン + リフレッシュトークン

  • アクセストークン: 短命(5〜15分程度)。API を叩くのに使う。盗まれても被害時間が短い
  • リフレッシュトークン: 長命。アクセストークンの再発行にだけ使う。サーバー側で保存・失効できるようにする
  • ローテーション: リフレッシュするたびに新しいリフレッシュトークンを発行し古いものは無効化。古いものが再利用されたら盗難とみなして一族ごと失効させる

仕組み② パスワード

自前でパスワードを扱う場合の最低ライン。

  • 平文・暗号化で保存しない。ハッシュ化する(元に戻せない形)
    • アルゴリズムは Argon2id(第一候補)か bcrypt。MD5・SHA-256 単体は速すぎて総当たりに弱いので不可
    • ソルト(ユーザーごとのランダム値)はライブラリが自動で付ける
  • ルールは NIST SP 800-63B の考え方が今の標準
    • 長さを重視(最低8文字、できれば15文字以上を許容・推奨)。長いパスフレーズを拒否しない
    • 「記号必須」などの複雑性ルールを強制しない、定期変更を強制しない(漏洩が疑われたときだけ変更)
    • 漏洩済みパスワードのリストと照合して弾く(Have I Been Pwned の API など)
    • パスワードマネージャーの貼り付けを妨げない
  • ログイン失敗時のメッセージは「メールアドレスまたはパスワードが違います」で統一(どちらが違うか教えると、登録済みメールアドレスを調べられてしまう=ユーザー列挙)

仕組み③ OAuth 2.0 と OpenID Connect(「Googleでログイン」)

  • OAuth 2.0: 本来は「権限を貸す」仕組み(例: このアプリに Google カレンダーの読み取りを許可する)。アクセストークンが発行される
  • OpenID Connect(OIDC): OAuth の上に「誰がログインしたか」を乗せた仕様。ID トークン(JWT)で本人情報が返る
  • 「〇〇でログイン」は認証なので OIDC。アクセストークンを「ログインの証拠」として扱うのは誤り

認可コードフロー + PKCE(今の標準)

1. アプリ → IdP へリダイレクト
   (client_id, redirect_uri, scope=openid email, state, nonce, code_challenge)
2. ユーザーが IdP でログインし、許可する
3. IdP → redirect_uri に「認可コード」を付けて戻す
4. アプリのサーバーが、認可コード + code_verifier を IdP に送ってトークンと交換
5. ID トークンの署名・iss・aud・exp・nonce を検証 → 自分のサービスのセッションを作る
  • PKCE: 認可コードを横取りされても、元の code_verifier を知らないと交換できない仕組み。モバイル・SPA だけでなく全クライアントで使うのが現在の推奨
  • state: CSRF 対策。行きと帰りで一致するか確認
  • redirect_uri は完全一致で登録。ワイルドカードや前方一致にしない(トークン横取りの原因)
  • 古い Implicit フロー(トークンを URL で直接返す)は非推奨。使わない
  • IdP 側の **sub(ユーザーの固定ID)**で自分のユーザーと紐づける。メールアドレスは変わりうるので主キーにしない

仕組み④ パスキー(WebAuthn / FIDO2)

パスワードの代わりに公開鍵暗号でログインする方式。

登録: 端末が鍵ペアを作る → 公開鍵だけをサーバーに保存(秘密鍵は端末から出ない)
ログイン: サーバーがランダムな値(チャレンジ)を送る
       → 端末が指紋・顔・PINで本人確認し、秘密鍵で署名
       → サーバーが公開鍵で検証
  • フィッシングに強い: 鍵がドメインに紐づくので、偽サイトでは署名が作れない
  • サーバーから漏れても困らない: 保存しているのは公開鍵だけ
  • iCloud キーチェーン / Google パスワードマネージャーで端末間同期されるので、機種変更でも消えにくい
  • 1つで「所持(端末)+ 生体 or 知識(ロック解除)」を満たすので、それ単体で多要素相当
  • 運用上は、端末紛失に備えて複数のパスキー登録や別の復旧手段を用意する

仕組み⑤ その他のログイン方式

方式仕組み注意
マジックリンク / メールOTPメールにリンクや6桁コードを送るメールアカウントが乗っ取られると終わり。リンクは短命・1回限り
TOTP(認証アプリ)30秒ごとに変わる6桁コード(Google Authenticator 等)MFA の定番。フィッシングサイトにコードを入力されると突破されうる
SMS コード電話番号にコード送信SIMスワップ等で弱い。他に手段がない場合の最終手段
プッシュ承認スマホに「ログインしますか?」通知通知を連打して誤承認させる攻撃(MFA疲労)→ 番号照合を併用

強さの目安: パスキー / セキュリティキー > TOTP > SMS > パスワードのみ

仕組み⑥ サーバー間・機械の認証

人ではなくプログラム同士の認証。

  • APIキー: 一番シンプル。漏れたら即ローテーション → APIキー・シークレット管理
  • OAuth クライアントクレデンシャルフロー: サービスが自分の client_id/secret で短命トークンを取得
  • 署名付きリクエスト / Webhook 署名: 送信元が共有鍵で HMAC 署名し、受信側が検証(LINE・Stripe 等の Webhook)
  • mTLS: クライアント側も証明書を出す。社内システムやゼロトラスト向け
  • MCP のリモートサーバーも OAuth 2.1 ベースで認可する(MCP)

仕組み⑦ モバイルアプリ(iOS)で気をつけること

  • トークンは Keychain に保存(UserDefaults は平文なので不可)
  • 外部ログインは ASWebAuthenticationSession(システムのブラウザ画面)を使う。アプリ内 WebView でパスワードを入力させない
  • App Store 審査の要件
    • Google 等のサードパーティログインを提供するなら、同等のプライバシー配慮があるログイン手段(Sign in with Apple など)も提供する(Guideline 4.8)
    • アカウント作成ができるアプリは、アプリ内からアカウント削除できるようにする(Guideline 5.1.1(v))
    • ログインが必要なアプリは審査用のデモアカウントを App Review Information に記載する
  • ログイン機能がないアプリは「サインイン情報: 不要」で済む。OS 標準の権限許可(MusicKit 等)はアプリ側の認証とは別物

実際の運用

1. 何を使うか決める

種類例向いている場合
ホスト型サービスClerk、Auth0、WorkOS、Firebase Auth、Supabase Auth早く・安全に作りたい。MFA・パスキー・管理画面が最初から付く。ユーザー数に応じて課金
ライブラリ(自分のDBに保存)Better Auth、Auth.jsデータを手元に置きたい、ランニングコストを抑えたい。運用責任は自分
ゼロトラスト型の門番Cloudflare Access社内ツール・管理画面に「特定メールの人だけ」通したい。アプリ側の実装ほぼ不要(Cloudflare)
自作—学習目的以外では選ばない

選ぶときの観点: 料金(MAU 課金の上がり方)、パスキー・MFA 対応、使うフレームワークの公式サンプル有無、ユーザーデータをエクスポートして乗り換えられるか(ベンダーロックイン)、日本語メールテンプレート、障害時の影響。

2. 初期設定で決めておくこと

  • セッション期限
    • 一般サービス: アイドル期限(しばらく操作がないと切れる)+ 絶対期限(最長でも何日)の2本立て
    • 管理画面・お金を扱う画面: 短め。重要操作(パスワード変更・送金・退会)の直前に再認証を求める
  • Cookie: HttpOnly / Secure / SameSite=Lax / 適切な Path と期限。サブドメインと共有するかは意識して決める
  • ログイン後にセッションIDを作り直す(セッション固定攻撃対策。ライブラリは通常自動)
  • リダイレクト先の制限: ?next=/dashboard は自サイト内パスのみ許可
  • 環境ごとに IdP のクライアントを分ける(開発・ステージング・本番で redirect_uri もシークレットも別)
  • メール送信: 確認メール・リセットメールの送信元ドメインに SPF / DKIM / DMARC を設定(迷惑メール行きと、なりすまし防止)

3. 日常的に回すこと

  • レート制限: ログイン・パスワードリセット・OTP 送信・新規登録に回数制限。IP 単位とアカウント単位の両方で。Cloudflare の Rate Limiting / Turnstile が手軽
    • アカウントを完全ロックすると嫌がらせに悪用されるので、段階的な待ち時間や CAPTCHA 追加が無難
  • 監査ログ: ログイン成功/失敗、MFA 設定変更、パスワード・メール変更、権限変更、管理者操作を、日時・IP・端末と一緒に記録
    • パスワード・トークン・OTP の値はログに出さない
  • ユーザーへの通知: 新しい端末からのログイン、パスワード・メールアドレス変更時にメールで知らせる(乗っ取りに本人が気づける)
  • **ログイン中の端末一覧と「他の端末からログアウト」**機能を提供する
  • 依存ライブラリ・認証 SDK の更新: 認証まわりの脆弱性修正は優先して当てる

4. 状態が変わるときの処理

イベントやること
ログアウトサーバー側のセッションを削除(Cookie を消すだけでは不十分)
パスワード変更・リセット他の全セッションとリフレッシュトークンを失効。完了通知メール
メールアドレス変更新アドレスで確認してから反映し、旧アドレスにも通知
権限変更・アカウント停止既存セッションにも即時反映(JWT だと期限まで残るので注意)
退会セッション・トークン失効、IdP 連携解除、データ削除/匿名化のポリシーに従う

5. アカウント復旧(一番弱くなりがちな入口)

どれだけログインを固くしても、「パスワードを忘れた」経路が弱ければそこから破られる。

  • リセットリンクは短命(15〜60分)・1回限り・推測不能なトークン。DB にはハッシュ化して保存
  • 「メールアドレスが登録されていれば送信しました」と、登録有無で応答を変えない
  • MFA を設定しているユーザーは、リセット後も MFA を要求する
  • MFA 紛失に備えてリカバリーコードを最初に発行し、保管を促す
  • サポート窓口での本人確認手順を決めておく(電話やチャットでの「なりすまし」によるリセットはよくある手口)

6. 鍵・シークレットのローテーション

  • JWT 署名鍵: kid(鍵ID)付きで JWKS として公開し、新鍵を追加 → 新鍵で署名開始 → 旧鍵で署名されたトークンが期限切れになったら旧鍵を削除、の順で無停止で切り替え
  • OAuth のクライアントシークレット・セッション暗号化キー・Webhook 署名鍵: 定期的に、かつ漏洩の疑いがあれば即時に交換。新旧を一時的に両方受け付けられる作りにしておくと安全
  • 置き場所は APIキー・シークレット管理 / Cloudflare_04_環境変数・シークレット・環境分離 のとおり。リポジトリや NEXT_PUBLIC_ には絶対に置かない

7. 障害・インシデント時

  • IdP / 認証サービスが落ちた場合: 新規ログインはできなくなる。既存セッションで使い続けられるか、ステータスページをどこで確認するかを把握しておく
  • 漏洩・乗っ取りが疑われる場合の手順を決めておく
    1. 影響範囲の特定(監査ログから)
    2. 該当ユーザー(または全員)のセッション・トークンを一括失効
    3. 必要ならパスワード強制リセット、関連シークレットのローテーション
    4. ユーザーへの通知(個人情報が絡む場合は法令上の報告義務も確認)
    5. 原因の修正と再発防止をログに残す

チェックリスト

実装

  • 認証はサービス/実績あるライブラリを使っている(自作していない)
  • セッションCookieに HttpOnly / Secure / SameSite
  • パスワードは Argon2id / bcrypt でハッシュ化、漏洩パスワードを拒否
  • OAuth/OIDC は認可コードフロー + PKCE、state・nonce 検証、redirect_uri 完全一致
  • ID トークン/JWT の署名・iss・aud・exp を検証、アルゴリズム固定
  • すべての API でサーバー側の認可チェック(本人のデータか)
  • ログイン失敗・パスワードリセットで登録有無が分からない
  • パスキーまたは MFA を提供している(管理者は必須)

運用

  • ログイン・リセット・OTP にレート制限
  • 認証イベントの監査ログ(秘密値は出力しない)
  • ログアウト・パスワード変更で全セッション失効
  • 新端末ログイン・重要変更時の通知メール
  • リセットトークンは短命・1回限り・ハッシュ保存
  • リカバリーコードと MFA 紛失時の手順がある
  • 署名鍵・シークレットのローテーション手順がある
  • 環境ごとに IdP クライアントとシークレットを分離
  • 漏洩時の一括失効の手順を試したことがある
  • (iOS)Sign in with Apple・アカウント削除・審査用デモアカウント

参考

  • NIST SP 800-63B: パスワード・MFA の考え方の国際的な基準
  • OWASP Authentication / Session Management Cheat Sheet: 実装時の具体的な注意点
  • OAuth 2.0 Security Best Current Practice(RFC 9700): OAuth の安全な使い方
  • passkeys.dev: パスキーの実装ガイド

関連