本文へ移動

ホーム / Web / 第4部 バックエンド

第4部 バックエンド・第11章

認証と認可

★★★ 必須9トピック・面接 5問

「誰か」を確かめる認証と、「何をしてよいか」を決める認可の実装を押さえる。

11.1セッションと JWT

用語★★★ 必須
ひとことで

セッションはサーバーに状態を持ち、JWT はトークン自体に情報と署名を持つ。失効のしやすさが大きな違い。

ログインしたユーザーを、次のリクエストでどう見分けるかの方法には、大きく2つあります。

  • セッション:サーバーが「このセッション ID はユーザー 42」と保存し、ブラウザには ID だけを Cookie で渡す。
  • JWT(JSON Web Token):ユーザー ID や期限をトークンの中に入れて署名する。サーバーは署名を確かめるだけで、保存先を引かなくてよい。中身は Base64 で、暗号化はされていない(誰でも読める)。
「誰か」をどう確かめるかセッションJWTブラウザサーバー保存先Redis・DBsid=abc引くabc → ユーザー 42・期限ログアウト:保存先から消せばすぐ効く全サーバーで保存先を共有するブラウザサーバー鍵JWTヘッダー . 中身(sub・exp). 署名失効:期限まで有効(短い期限+リフレッシュで補う)中身は誰でも読める。秘密を入れない
セッションと JWT の確かめ方の違い
項目セッションJWT
状態の置き場所サーバー(DB・Redis)トークンの中
失効(ログアウト・強制停止)サーバーで消せばすぐ効く期限まで有効。すぐ止めるには拒否リストなど結局サーバーの状態が要る
スケール保存先を全サーバーで共有する(Redis など)保存先が要らず、別サービスでも鍵があれば確かめられる
大きさID だけで小さい情報を入れるほど大きく、毎回送られる
向いているもの普通の Web サイト(同じドメインのブラウザ)サービス間の認証、短い期限のアクセストークン、OAuth

面接ではこう答える

ログインしたユーザーを見分ける方法には、セッションと JWT があります。セッションはサーバーがセッション ID とユーザーの対応を保存し、ブラウザには ID だけを Cookie で渡す方法で、サーバーで消せばすぐに失効できます。JWT はユーザー ID や期限をトークンに入れて署名する方法で、保存先を引かずに確かめられますが、期限まで失効させにくいです。同じドメインの普通の Web アプリなら、まずセッションで十分なことが多いです。

ひとことで

JWT はすぐには失効できず、中身は誰でも読める。期限を短くし、リフレッシュトークンをサーバーで管理し、署名と宛先を必ず検証する。

アクセストークンは短く、リフレッシュはサーバーで管理アクセストークン15分15分15分15分15分リフレッシュ数日〜数週間。使うたびに新しくし、サーバーで失効できる盗まれても、アクセストークンは短時間で使えなくなる。リフレッシュの使い回しは検知して止める
アクセストークンとリフレッシュトークンの期限
  • 保存場所:localStorage に置くと、XSS があれば JS から盗まれる。ブラウザなら HttpOnly; Secure; SameSite の Cookie に入れるほうが盗まれにくい(その場合は CSRF 対策も要る)。
  • 失効:アクセストークンの期限は短く(5〜15分程度)し、長く使うリフレッシュトークンはサーバーで管理して失効できるようにする。リフレッシュトークンは使うたびに新しくする(ローテーション)と、盗まれたことに気づきやすい。
  • 検証:署名のアルゴリズムを固定する(alg: none や想定外のアルゴリズムを受け付けない)、exp(期限)・iss(発行者)・aud(宛先)を確かめる。実績のあるライブラリを使う。
  • 中身は誰でも読めるので、個人情報や秘密を入れない。

同じドメインの普通の Web アプリなら、まずはセッションで十分なことが多いです。「JWT ならスケールする」だけを理由に選ぶと、失効の難しさを後から抱えます。

面接ではこう答える

JWT は署名で改ざんを防げますが、中身は Base64 なので誰でも読めますし、一度発行すると期限まで有効です。そこでアクセストークンの期限は5〜15分程度と短くし、長く使うリフレッシュトークンはサーバーで管理して、使うたびに新しくします。検証では、アルゴリズムを固定し、exp・iss・aud を必ず確かめます。ブラウザでは localStorage より HttpOnly の Cookie に入れるほうが XSS で盗まれにくいです。

11.3OAuth 2.0 / OIDC

用語★★★ 必須
ひとことで

パスワードを渡さずに、他のサービスでログインしたり権限を委譲したりする仕組み。

OAuth 2.0 は、ユーザーのパスワードを渡さずに、別のアプリに「この範囲の操作だけしてよい」という許可(アクセストークン)を与える仕組みです(認可)。OIDC(OpenID Connect)は OAuth の上に「この人は誰か」を伝える仕組みを足したもので、「Google でログイン」のような認証に使います。

認可コードフロー+PKCE

今の Web・スマホのアプリでは、このフローが標準です。

  • アプリが、ランダムな code_verifier を作り、そのハッシュ(code_challenge)と、ランダムな state を用意する。
  • ユーザーを認可サーバー(Google など)へリダイレクトする:/authorize?response_type=code&client_id=…&redirect_uri=…&scope=openid email&state=…&code_challenge=…&code_challenge_method=S256
  • ユーザーがログインして許可すると、redirect_uri?code=…&state=… に戻ってくる。
  • アプリは state が自分の送ったものと同じか確かめる(違えば CSRF の可能性があるので止める)。
  • アプリのサーバーが code と code_verifier をトークンエンドポイントに送り、アクセストークン(と ID トークン・リフレッシュトークン)を受け取る。
認可コードフロー+PKCEブラウザ(ユーザー)アプリのサーバー認可サーバーstate と code_verifier を作るリダイレクト(challenge・state 付き)GET /authorize?…&code_challenge=…&state=…ログイン・同意 → redirect_uri?code=…&state=…code と state を渡すstate が送ったものと同じか確かめるcode + code_verifier でトークンを求めるverifier のハッシュ=challenge か確かめるアクセストークン・ID トークン
認可コードフロー+PKCE の流れ
仕組み防ぐもの
state攻撃者の認可コードでログインさせられる(ログイン CSRF)
PKCE(code_verifier)リダイレクトの途中で認可コードを盗まれても、verifier を知らないとトークンに換えられない
redirect_uri の完全一致の登録コードを攻撃者のサイトに送らせる
nonce(OIDC)ID トークンの使い回し

OAuth のセキュリティの指針(RFC 9700)では、PKCE は公開クライアント(SPA・スマホアプリなど秘密を持てないもの)では必須、サーバーで秘密を持てるアプリでも推奨とされています。トークンを URL で直接返す「インプリシットフロー」は使わないよう勧められています。

面接ではこう答える

OAuth 2.0 は、ユーザーのパスワードを渡さずに、別のアプリに決まった範囲の操作だけを許可する仕組みで、OIDC はその上にログインした人が誰かを伝える仕組みを足したものです。今の標準は認可コードフローと PKCE で、アプリは state と code_challenge を付けて認可サーバーへリダイレクトし、戻ってきたコードを code_verifier と一緒にトークンに換えます。state は CSRF を、PKCE はコードの横取りを防ぎます。

別のページで開く →

ネットワーク図を読み込んでいます…

ひとことで

ID トークンはアプリに「誰がログインしたか」を伝える JWT、アクセストークンは API を呼ぶための許可証。取り違えない。

「誰か」を伝える札と、「何をしてよいか」の許可証ID トークン(OIDC)・宛先はアプリ(クライアント)・ログインした人が誰か・JWT。sub・iss・aud・nonceアクセストークン・宛先は API(リソース)・scope の範囲の操作を許す・形式は決まっていないリフレッシュトークン・宛先は認可サーバー・新しいトークンをもらう・長め。厳重に保管
3つのトークンの宛先と役割
  • ID トークン(OIDC):「誰がログインしたか」をアプリに伝える JWT。sub(ユーザーの一意の ID)・iss・aud(自分のクライアント ID か)・exp・nonce を確かめてから使う。ユーザーを結びつけるのはメールアドレスではなく iss と sub の組で。
  • アクセストークン:API を呼ぶための許可証。scope で範囲が決まる。宛先の API が確かめるもので、ログインの証明に使ってはいけない。

面接ではこう答える

OIDC のログインでは、2種類のトークンを受け取ります。ID トークンはアプリ宛ての JWT で、ログインした人が誰かを伝えます。iss・aud・exp・nonce を確かめ、ユーザーは iss と sub の組で識別します。アクセストークンは API 宛ての許可証で、scope の範囲の操作を許すものです。アクセストークンをログインの証明に使うと、他のアプリ向けのトークンで入られる危険があるので、取り違えないようにします。

11.5パスワードの保存

用語★★☆ 重要
ひとことで

パスワードは Argon2id・bcrypt などの専用のハッシュで保存する。平文・暗号化・速いハッシュは使わない。

元に戻せず、わざと遅いハッシュで保存するパスワード平文は保存しないソルトを足すユーザーごとに乱数専用のハッシュArgon2id・bcryptハッシュを保存漏れても戻しにくい
パスワードを保存するまでの流れ

パスワードの保存方法

パスワードは、元に戻せないパスワード専用のハッシュ関数で保存します。漏れても元のパスワードが分かりにくいようにするためです。

  • 使う:Argon2id・bcrypt・scrypt。わざと遅く作られていて、総当たりに時間がかかる。ユーザーごとのランダムな値(ソルト)も自動で入る。
  • 使わない:平文、暗号化(鍵があれば戻せる)、MD5・SHA-256 をそのまま(速すぎて総当たりされる)。
  • ハッシュの強さ(コスト)は、サーバーで1回 数十〜数百ms になる程度に調整する。
  • ログインには回数制限を付け、「メールアドレスが違います」と「パスワードが違います」を分けずに伝える。漏えいしたパスワードの一覧と照合して弱いものを断るのも有効。

面接ではこう答える

パスワードは、元に戻せず、わざと遅く作られたパスワード専用のハッシュ関数で保存します。使うのは Argon2id や bcrypt で、ユーザーごとのソルトも自動で入ります。平文や、鍵で戻せる暗号化、速すぎる MD5 や SHA-256 をそのまま使うのは避けます。ログインには回数制限を付け、漏えいしたパスワードの一覧と照合して弱いものを断るのも有効です。

11.6多要素認証(MFA)

用語★★☆ 重要
ひとことで

パスワードに、持っているもの・本人の体などの別の要素を足す。SMS や TOTP は偽サイトに弱く、パスキーが最も強い。

偽サイト(フィッシング)への強さは方式で大きく違うSMS の確認コード偽サイトに入力させられるTOTP(認証アプリ)同じく入力させられるプッシュ通知の承認連打されて押してしまうパスキー・セキュリティキードメインに結びつき強い
MFA の方式ごとのフィッシングへの強さ(イメージ)

多要素認証は、パスワード(知っているもの)に、別の種類の要素(持っているもの・本人の体)を足す仕組みです。

パスワードに加えて、別の要素(持っているもの・本人の体)を求めます。

方式しくみ弱いところ
SMS の確認コード電話番号に届く数字を入れるSIM の乗っ取り、偽サイトに入力させるフィッシング
TOTP(認証アプリ)アプリが30秒ごとに作る6桁の数字偽サイトに入力させるフィッシングには弱い
パスキー端末の秘密鍵で署名する端末をなくしたときの復旧手段を用意する必要がある

MFA を入れたら、端末をなくしたときの回復の手段(回復コード・別の要素の登録)が攻撃の抜け道にならないよう、同じくらい慎重に作ります。

面接ではこう答える

多要素認証は、パスワードだけでなく、スマホや本人の指紋のような別の種類の要素を求める仕組みです。SMS のコードや認証アプリの TOTP は広く使われていますが、偽サイトにコードを入力させられるとそのまま使われてしまいます。パスキーやセキュリティキーは鍵がサイトのドメインに結びつくので、偽サイトでは使えず、フィッシングに強いです。回復の手段が抜け道にならないことも大事です。

11.7パスキー(WebAuthn)

用語★★☆ 重要
ひとことで

端末の中の秘密鍵で署名してログインする仕組み。サーバーには公開鍵だけを置き、鍵はドメインに結びつくのでフィッシングに強い。

パスキーは、端末の中で作った公開鍵と秘密鍵の組でログインする仕組みです。サーバーには公開鍵だけを保存し、ログインのたびに端末が秘密鍵で署名します。秘密鍵は端末から出ず、指紋や顔・PIN で使います。

パスキー:秘密鍵は端末から出ない端末(ブラウザ)サーバー(公開鍵を保存)チャレンジ(毎回のランダム値)指紋・顔・PIN で秘密鍵を使うチャレンジへの署名(このドメイン用の鍵)保存した公開鍵で署名を確かめる
パスキー(WebAuthn)でログインする流れ
  • サーバーに漏れて困るパスワードがない。
  • 鍵はサイトのドメインに結びつくので、偽サイトでは使えず、フィッシングに強い。
  • 実装はブラウザの navigator.credentials.create()/get() と、サーバー側の検証ライブラリ(SimpleWebAuthn など)を使う。端末をなくしたときの復旧手段(別のパスキー・回復手段)を用意しておく。
パスワードパスキー
サーバーに保存するものハッシュ(漏れると総当たりされる)公開鍵(漏れても困らない)
偽サイト入力すれば盗まれるドメインが違うので使えない
使い回し起きやすいサイトごとに別の鍵

面接ではこう答える

パスキーは、端末の中で作った公開鍵と秘密鍵の組でログインする仕組みで、WebAuthn という標準で動きます。サーバーには公開鍵だけを保存し、ログインのたびにサーバーが出した値に端末が秘密鍵で署名します。秘密鍵は端末から出ず、指紋や顔で使います。鍵はサイトのドメインに結びつくので、偽サイトでは使えません。端末をなくしたときの回復手段を用意するのが実装のポイントです。

11.8認可(権限)の設計

用語★★☆ 重要
ひとことで

ロールや属性で「誰が何をできるか」を決める。チェック漏れ(アクセス制御の不備)が最も多い脆弱性。

認証は「誰か」を確かめること、認可は「その人がそれをしてよいか」を決めることです。認可の漏れは OWASP Top 10 でも一番上にある、よく起きる問題です。

何で「してよいか」を決めるかRBAC・役割で決める・管理者・編集者・閲覧者・まずはここからABAC・属性で決める・部署・状態・時間・細かい条件を書けるReBAC・関係で決める・持ち主・共有された人・ドキュメント共有など
認可の3つの考え方

RBAC・ABAC

方式考え方例
RBAC(ロールベース)ユーザーに役割を付け、役割ごとに許す操作を決める管理者・編集者・閲覧者
ABAC(属性ベース)ユーザー・データ・状況の属性で判定する「同じ部署」かつ「公開前」の記事だけ編集できる
関係ベース(ReBAC)ユーザーとデータの関係で決めるこのフォルダの持ち主・共有された人

多くのアプリは RBAC から始め、足りない所に「自分のデータか」などの条件を足します。判定の処理は1か所(ポリシーの関数・Pundit・CASL など)にまとめ、各 API に散らばらないようにします。

面接ではこう答える

認証は誰かを確かめること、認可はその人がそれをしてよいかを決めることです。よく使うのは、役割で決める RBAC で、そこに部署や状態のような属性で決める ABAC や、持ち主や共有の関係で決める考え方を足します。判定の処理はポリシーの関数のように1か所にまとめ、各 API に散らばらないようにします。画面でボタンを隠すだけでは守れないので、必ずサーバーで確かめます。

ひとことで

URL や本文の ID を変えるだけで他人のデータに届いてしまう問題。すべての API で、持ち主か権限があるかをサーバーで確かめる。

IDOR(安全でない直接オブジェクト参照)は、GET /orders/1001 の ID を 1002 に変えるだけで、他人の注文が見えてしまう問題です。ログインしているかしか確かめていないと起こります。

ログインの確認だけでは足りないGET /orders/1002ユーザー A(ログイン済み)ログインしている?持ち主・権限は?WHERE user_id = 自分200自分のもの404他人のものここを飛ばすと IDORID を変えるだけで他人のデータが見えるこれだけだと
IDOR を防ぐ確認の順番
// 危ない
const order = await db.order.findUnique({ where: { id } });
// 安全:持ち主の条件を付ける
const order = await db.order.findFirst({ where: { id, userId: currentUser.id } });
if (!order) return res.status(404).end();
  • すべての API で、データの持ち主か・権限があるかをサーバーで確かめる。画面でボタンを隠すだけでは守れない。
  • マルチテナントでは、tenant_id の条件を必ず付ける仕組み(共通のスコープ・PostgreSQL の行レベルセキュリティ)にする。
  • 推測しにくい ID(UUID)にするのは助けになるが、権限の確認の代わりにはならない。
  • 「他のユーザーで見ると 404 になる」ことをテストに書く。

面接ではこう答える

IDOR は、GET /orders/1001 の ID を 1002 に変えるだけで他人の注文が見えてしまうような問題で、ログインしているかしか確かめていないと起こります。対策は、すべての API で、データの持ち主か権限があるかをサーバーで確かめることです。クエリに持ち主の条件を付け、なければ 404 を返します。マルチテナントでは tenant_id の条件を共通の仕組みで必ず付け、他人のユーザーで試すテストを書きます。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • セッションと JWT の違いと使い分け
  • OAuth 2.0 / OIDC の流れと、認可(権限)の設計
  • セッションと JWT:セッションはサーバーに状態を持ち、JWT はトークン自体に情報と署名を持つ。失効のしやすさが大きな違い。
  • JWT を使うときの注意(保存場所・失効・検証):JWT はすぐには失効できず、中身は誰でも読める。期限を短くし、リフレッシュトークンをサーバーで管理し、署名と宛先を必ず検証する。
  • OAuth 2.0 / OIDC:パスワードを渡さずに、他のサービスでログインしたり権限を委譲したりする仕組み。
  • ID トークンとアクセストークン:ID トークンはアプリに「誰がログインしたか」を伝える JWT、アクセストークンは API を呼ぶための許可証。取り違えない。
  • パスワードの保存:パスワードは Argon2id・bcrypt などの専用のハッシュで保存する。平文・暗号化・速いハッシュは使わない。
  • 多要素認証(MFA):パスワードに、持っているもの・本人の体などの別の要素を足す。SMS や TOTP は偽サイトに弱く、パスキーが最も強い。
  • パスキー(WebAuthn):端末の中の秘密鍵で署名してログインする仕組み。サーバーには公開鍵だけを置き、鍵はドメインに結びつくのでフィッシングに強い。
  • 認可(権限)の設計:ロールや属性で「誰が何をできるか」を決める。チェック漏れ(アクセス制御の不備)が最も多い脆弱性。
  • IDOR(他人のデータが見えてしまう)を防ぐ:URL や本文の ID を変えるだけで他人のデータに届いてしまう問題。すべての API で、持ち主か権限があるかをサーバーで確かめる。

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1セッションと JWT の違いは?

模範回答

セッションはサーバー側にログイン状態を保存し、ブラウザには ID だけを Cookie で渡します。JWT はユーザー ID や期限をトークンの中に入れて署名したもので、サーバーは署名を確かめるだけで済みます。

一番の違いは失効です。セッションはサーバーで消せばすぐログアウトできますが、JWT は期限まで有効なので、すぐ止めたいなら期限を短くしてリフレッシュトークンをサーバーで管理する必要があります。スケールの面では JWT は保存先が要らず別サービスでも検証できますが、セッションも Redis で共有すれば十分スケールします。保存場所は、localStorage だと XSS で盗まれやすいので、どちらでも HttpOnly の Cookie が基本です。同じドメインの Web アプリならセッション、サービス間の認証や OAuth のアクセストークンなら JWT を選びます。

深掘りされたら

  • リフレッシュトークンが盗まれたらどう気づきますか?

見られているポイント失効・保存場所・スケールのトレードオフ

Q2OAuth の認可コードフローを説明してください。

模範回答

アプリはまずランダムな state と code_verifier を作り、verifier のハッシュを code_challenge として、ユーザーを認可サーバーへリダイレクトします。ユーザーがログインして許可すると、認可コードと state を付けて redirect_uri に戻ってきます。アプリは state が自分の送ったものと同じか確かめ、サーバーから認可コードと code_verifier をトークンエンドポイントに送って、アクセストークンや ID トークンを受け取ります。

state は、攻撃者の認可コードでログインさせられる CSRF を防ぐためのものです。PKCE は、リダイレクトの途中で認可コードが盗まれても、verifier を知らない攻撃者はトークンに換えられないようにするためのもので、今はサーバーのあるアプリでも付けるのが推奨です。OIDC で ID トークンを受け取ったら、署名・iss・aud・exp・nonce を確かめてから、sub でユーザーを特定します。

深掘りされたら

  • ID トークンとアクセストークンの違いは?

見られているポイントPKCE と state の役割

Q3パスワードはどう保存しますか?

模範回答

パスワード専用のハッシュ関数である Argon2id や bcrypt で保存します。元に戻せない形にし、わざと計算を遅くして総当たりに時間がかかるようにするためです。ユーザーごとのランダムな値(ソルト)も入るので、同じパスワードでも違う値になります。

平文や、鍵で戻せる暗号化、MD5 や SHA-256 をそのまま使うのはよくありません。速すぎて、漏れたときに総当たりで元のパスワードを見つけられてしまうからです。加えて、ログインに回数制限を付け、漏えいしたパスワードの一覧と照らして弱いものを断り、多要素認証やパスキーを用意します。

深掘りされたら

  • パスキーがフィッシングに強いのはなぜですか?

見られているポイント専用のハッシュを使う理由と、周りの対策

Q4URL の ID を変えると他人のデータが見えてしまう問題(IDOR)をどう防ぎますか?

模範回答

すべての API で、ログインしているかだけでなく、そのデータの持ち主か、見る権限があるかをサーバーで確かめます。具体的には、取得の条件に user_id = 自分 やテナントの ID を必ず入れ、見つからなければ 404 を返します。画面でボタンを隠すだけでは、API を直接呼ばれると守れません。

漏れを防ぐために、権限の判定を1か所にまとめる、テナントの条件を自動で付ける仕組み(共通のスコープや行レベルセキュリティ)を使う、他のユーザーで呼ぶと 404 になることをテストに書く、といったことをします。UUID のように推測しにくい ID にするのは助けになりますが、権限の確認の代わりにはなりません。

深掘りされたら

  • 403 と 404 のどちらを返しますか?

見られているポイントサーバーでの持ち主の確認と、漏れを防ぐ仕組み

Q5「Google でログイン」を実装するとき、何に気をつけますか?

模範回答

OIDC の認可コードフローに PKCE と state を付けて実装し、実績のあるライブラリを使います。戻ってきた ID トークンは、署名・発行者(iss)・宛先(aud が自分のクライアント ID か)・期限(exp)・nonce を確かめてから使います。

ユーザーを結びつけるのは、メールアドレスではなく、iss と sub の組です。メールアドレスは変わることがあり、既存のパスワードのアカウントに自動でつなぐと乗っ取りに使われることがあるので、つなぐときは本人確認を挟みます。redirect_uri は完全一致で登録し、クライアントシークレットはサーバーだけに置きます。

深掘りされたら

  • ID トークンを API の認可に使ってはいけないのはなぜですか?

見られているポイントトークンの検証と、アカウントの結びつけ方