第1部 設計・アーキテクチャ
第3章 認証・認可アーキテクチャ — OAuth 2.0 / OIDC / ゼロトラスト
「誰であるか」を確かめる認証と、「何をしてよいか」を決める認可。この2つを現代のアーキテクチャでどう組み立てるかを、OAuth 2.0・OIDC・ゼロトラストを軸に整理します。
🎯 この章で学ぶこと
- セッション方式とトークン方式の違いと、それぞれの向き不向き
- OAuth 2.0は「認可の委譲」であって認証ではない、という最重要ポイント
- OIDC・SAMLの役割分担と、JWTの扱い方(保管・失効・短命化)
- RBAC/ABACと最小権限、そしてゼロトラストの「常に検証」という発想
3.1 認証と認可の再確認 — セッション方式とトークン方式
入門コース第9章のとおり、認証(Authentication)は「あなたは誰か」を確かめること、認可(Authorization)は「あなたに何を許すか」を決めることです。ログインは認証、「この管理画面を開いてよいか」の判定は認可です。この区別は本章の全体を貫く土台なので、まず頭を切り替えておいてください。認証に成功したからといって、あらゆる操作が許されるわけではありません。
いったん認証したユーザーの状態を、リクエストをまたいでどう覚えておくか。ここに2つの代表的な方式があります。
| セッション方式(ステートフル) | トークン方式(ステートレス) | |
|---|---|---|
| 状態の持ち方 | サーバー側にセッション情報を保存し、CookieにセッションIDを渡す | 署名付きトークン自体に情報を入れ、サーバーは状態を持たない |
| 検証 | 毎回サーバーのストアを参照 | 署名を検証するだけで完結(DB参照不要) |
| 失効(無効化) | 容易(サーバー側で消せば即無効) | 難しい(発行済みトークンは有効期限まで生きる) |
| スケール | 共有セッションストアが必要になりがち | 水平スケールしやすい、マイクロサービス向き |
| 向く場面 | 単一サービス、失効を厳密に管理したい | API・SPA・複数サービス連携 |
「トークンは新しくて優れている」という単純な話ではありません。トークン方式の最大の弱点は「失効の難しさ」です。サーバーが状態を持たない代償として、盗まれたトークンを即座に無効化しにくくなります(3.4で対策を扱います)。設計では「失効をどこまで厳密にやりたいか」で選び分けます。
3.2 OAuth 2.0 — 認可の委譲
ここが本章で最も誤解される点です。OAuth 2.0は「認可の委譲」の仕組みであって、認証(ログイン)の仕組みではありません。OAuthが解くのは「ユーザーが、あるアプリに対して、別のサービスにある自分のデータへのアクセスを、パスワードを渡さずに許可する」という問題です。
身近な例で言えば、「新しい年賀状アプリに、Googleの連絡先を読ませたい。でもGoogleのパスワードは絶対に渡したくない」——この要求を安全にかなえるのがOAuthです。登場人物は4者です。
| 役割 | 意味 | 上の例で言うと |
|---|---|---|
| リソースオーナー | データの持ち主。許可を出す本人 | あなた(ユーザー) |
| クライアント | データにアクセスしたいアプリ | 年賀状アプリ |
| 認可サーバー | 本人確認し、許可(トークン)を発行する | Googleの認可サーバー |
| リソースサーバー | データを保持し、トークンを検証して渡す | Googleの連絡先API |
最も基本的で推奨される流れが認可コードフロー(Authorization Code Flow)です。概略は次のとおりです。
1. クライアントが利用者を認可サーバーへリダイレクト(欲しい権限= scope を指定)
2. 認可サーバーが利用者を認証し、「連絡先の読み取りを許可しますか?」と同意を求める
3. 利用者が同意すると、認可サーバーは「認可コード」をクライアントに返す(ブラウザ経由)
4. クライアントは認可コードを、自身の秘密情報とともに認可サーバーへ送る(サーバー間・裏側)
5. 認可サーバーが「アクセストークン」(と場合により「リフレッシュトークン」)を発行
6. クライアントはアクセストークンを付けてリソースサーバーのAPIを呼ぶ
ポイントは、ブラウザ経由で渡るのは短命な認可コードだけで、実際に効力を持つアクセストークンはサーバー間のやり取りで受け取る点です。これによりトークンの露出を最小化します。現在はさらに、認可コードの横取りを防ぐPKCE(Proof Key for Code Exchange)の併用が、Webアプリ・モバイルを問わず標準的な推奨になっています。
用語も押さえておきましょう。scopeは「このトークンで何ができるか」の範囲(例: contacts.readonly)で、最小権限の実装そのものです。アクセストークンはAPI呼び出しに使う短命な鍵、リフレッシュトークンはアクセストークンが切れたとき再発行を受けるための長命な鍵です。
OAuthが返すアクセストークンは「このデータへのアクセスを許された」ことしか意味しません。「そのトークンを提示した相手が本人である」ことは保証しないのです。トークンを認証代わりに使うと、別アプリ向けに発行されたトークンを流用される(トークン置換攻撃)といった問題が起こり得ます。「アクセスできる=本人だ」と考えてはいけません。認証したいなら、次に述べるOIDCを使います。
3.3 OpenID Connect と SAML — 認証の層
「本人であること」を確かめたい——つまり認証がしたいなら、OAuth 2.0の上に認証層を乗せたOpenID Connect(OIDC)を使います。OIDCはOAuth 2.0を拡張し、認可コードフローの結果としてIDトークンを追加で発行します。IDトークンは「いつ、誰が、どの認証方式で認証されたか」を含むJWTで、これこそが「本人である」ことの証明になります。
覚え方はシンプルです。OAuthは「何にアクセスしてよいか(認可)」、OIDCは「あなたが誰か(認証)」。世に言う「Googleでログイン」「Appleでサインイン」のようなソーシャルログインは、OAuthではなくOIDCで実装するのが正解です。標語にすれば——「OAuthで認証するな、OIDCを使え」。
エンタープライズの世界では、もう一つSAML(Security Assertion Markup Language)が広く使われています。SAMLはXMLベースの古参規格で、社内システム群へのシングルサインオン(SSO)で根強く現役です。役割はOIDCと重なりますが、以下のように住み分けています。
| OpenID Connect(OIDC) | SAML | |
|---|---|---|
| ベース | OAuth 2.0 / JSON / JWT | XML |
| 主な用途 | Web/モバイル/API、コンシューマ向けログイン | 企業内SSO、SaaSとの連携 |
| 相性 | モバイル・SPA・軽量なAPI連携に強い | 既存のエンタープライズ製品群に強い |
新規のWeb/モバイルなら基本はOIDC、既存の社内SSO基盤に合わせるならSAML、と考えておけば実務上は困りません。
3.4 トークンの取り扱い — JWTの構造と失効の難しさ
OIDCのIDトークンも、多くのアクセストークンも、実体はJWT(JSON Web Token)であることが多いので、その構造を押さえます。JWTはドットで区切られた3部構成です。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjM0Iiwicm9sZSI6InVzZXIifQ . 署名
header(ヘッダー) payload(クレーム) signature
header : 署名アルゴリズム(alg)など
payload : sub(誰か)、exp(有効期限)、role/scope などのクレーム(=主張)
signature: header + payload を鍵で署名したもの。改ざん検知に使う
ここで決定的に重要なのは、payloadは「署名されている」だけで「暗号化されている」わけではないという点です。Base64URLでデコードすれば中身は誰でも読めます。したがってJWTに秘密情報(パスワード、個人情報)を入れてはいけません。署名が保証するのは「改ざんされていないこと」であって「秘匿」ではありません。
トークンの保管場所も設計判断です。ブラウザでの選択肢と性質を整理します。
| 保管場所 | 長所 | 短所・注意 |
|---|---|---|
| HttpOnly Cookie | JavaScriptから読めずXSSでの窃取に強い | CSRF対策(SameSite等)が別途必要 |
| localStorage | 実装が簡単、CSRFの影響を受けにくい | XSSで容易に盗まれる。機微なトークンの保管先としては非推奨 |
そして最大の課題が失効の難しさです。署名で自己完結するトークンは、サーバーが状態を持たないぶん、発行後に「やっぱり無効」とするのが困難です。有効期限(exp)まで、盗まれたトークンは生き続けます。実務での定石はこうです。
- アクセストークンは短命に(数分〜十数分)。盗まれても被害の窓を小さくする
- リフレッシュトークンで更新。こちらは長命だが、安全な場所(サーバー側・HttpOnly)に置き、失効管理する
- 緊急の無効化が要る場面では、失効リスト(拒否リスト)やトークンIDのDB照合を併用(=一部ステートフルに戻す)
「ステートレスの利点」と「失効の必要性」はトレードオフです。第1章で述べたとおり、セキュリティは常にこうした引き換えを引き受ける営みです。
3.5 認可モデル — RBAC・ABAC・最小権限
認証が済んだら、次は「何を許すか」=認可です。代表的な設計が2つあります。
- RBAC(Role-Based Access Control):役割ベース。ユーザーに「管理者」「編集者」「閲覧者」といった役割を割り当て、役割ごとに権限を決める。シンプルで管理しやすく、多くのシステムの標準。
- ABAC(Attribute-Based Access Control):属性ベース。ユーザー・リソース・環境の属性(部署、所有者、時間帯、アクセス元)を組み合わせて動的に判定する。きめ細かいが複雑。「所有者本人のときだけ編集可」のような条件はABAC的な発想。
どちらを採るにせよ、貫くべき原則は最小権限(Least Privilege)です。ユーザーもプロセスも、仕事に必要な最小限の権限だけを持たせる。これは第2章のアタックサーフェス削減の、認可における現れです。
実は、実際のWeb APIで最も頻繁に見つかる深刻な欠陥は、派手な攻撃ではなく認可チェックの単純な抜けです。/api/orders/1001 の 1001 を 1002 に変えただけで他人の注文が見えてしまう——これがIDOR(Insecure Direct Object Reference)、API文脈ではBOLA(Broken Object Level Authorization)と呼ばれる問題です。原因は「認証は通っているが、そのオブジェクトがそのユーザーのものか」というサーバー側チェックの欠落。すべてのデータアクセスで「このユーザーは、このリソースに、この操作をしてよいか」を毎回サーバー側で確かめる——これが認可の鉄則です。URLやフォームの値を信用してはいけません(入門コース第10章の「入力を信頼しない」原則の延長線上にあります)。
3.6 ゼロトラスト — 「常に検証する」
最後に、これらを束ねる現代の設計思想がゼロトラストです。従来は「社内ネットワークの中は信頼できる」という境界防御(城と堀のモデル)が前提でした。ゼロトラストはこの前提を捨てます。合言葉は「決して信頼せず、常に検証せよ(Never trust, always verify)」。第1章の原則3(侵害前提=Assume Breach)を、アクセス制御に落とし込んだものと言えます。
ゼロトラストの具体的な考え方は次のとおりです。
- ネットワークの場所で信頼しない。社内からのアクセスでも、無条件には信頼しない
- すべてのアクセスを毎回検証する。一度認証したら通し、ではなく、リクエストごとに確かめる
- ID・デバイス・コンテキストで判断する。誰が(ID)、どの端末で(デバイスの健全性)、どんな状況で(場所・時刻・普段と違う挙動か)を総合評価する
- 侵害されている前提で設計する。すでに内部に敵がいるとしても被害を最小化できるよう、最小権限とセグメンテーションを効かせる
ゼロトラストは特定の製品を買えば実現するものではなく、ID中心の認証・認可(本章の前半)、最小権限、継続的な検証、監視(第7章)を組み合わせて到達する設計のあり方です。「社内だから安全」という思い込みを捨てることが、その第一歩になります。
本章の技術は、いずれも自前で実装すると必ずと言っていいほど穴が開く領域です。パスワード保存、セッション管理、トークン署名の検証、OAuth/OIDCのフロー——これらは長年の攻防を経て標準化された、地雷だらけの分野です。「自社の要件は特別だから独自に作る」という判断は、ほぼ常に間違いです。実績あるIdP(Identity Provider)やライブラリ、フレームワーク標準の認証機構を使い、車輪の再発明を避けてください。あなたが書くべきなのは認証の中身ではなく、「必要な認可チェックを、すべての入口で漏れなく呼ぶこと」です。3.5のIDOR対策こそ、開発者が自分の手で守るべき最前線です。
まとめ
- 状態の持ち方でセッション方式とトークン方式に分かれ、トークンは失効の難しさが代償
- OAuth 2.0は「認可の委譲」であって認証ではない。認証したいならOIDCを使う
- JWTは署名されているだけで暗号化ではない。秘密情報を入れず、短命化+リフレッシュで運用
- 認可はRBAC/ABACと最小権限で設計し、IDOR/BOLA(認可チェック漏れ)を全入口で防ぐ
- ゼロトラストは「社内なら信頼」を捨て、ID・デバイス・コンテキストで常に検証する
練習問題
あるエンジニアが「ソーシャルログイン(◯◯でログイン)をOAuth 2.0のアクセストークンで実装した。トークンが取れたユーザーをログイン済みとして扱っている」と言っています。この設計の何が問題で、どう直すべきでしょうか。
解答を見る
OAuthを認証に流用しているのが問題です。アクセストークンは「あるデータへのアクセスが許された」ことしか意味せず、「そのトークンを提示した相手が本人である」ことを保証しません。別のクライアント向けに発行されたトークンを流用される(トークン置換)といった攻撃の余地が生まれます。正しくはOpenID Connect(OIDC)を使い、認可コードフローで発行されるIDトークン(JWT)を、署名・発行者(iss)・想定オーディエンス(aud)まで検証して本人性を確認します。標語で言えば「OAuthで認証するな、OIDCを使え」です。
「マイクロサービス構成で水平スケールしたいので、失効のためのDB参照をなくし、有効期限を24時間にした長命なJWTアクセストークンだけで運用したい」という提案があります。セキュリティ上の懸念と、現実的な折衷案を述べてください。
解答を見る
懸念:JWTは発行後の失効が難しいため、有効期限を24時間と長くすると、トークンが盗まれた場合に丸1日、攻撃者に有効なアクセスを許してしまいます。ログアウトや権限剥奪も即座には反映されません。折衷案:アクセストークンを短命(数分〜十数分)にして被害の窓を狭め、長命なリフレッシュトークンで更新する設計にします。リフレッシュトークンは安全な場所に保管し、失効管理(拒否リストやトークンIDのDB照合)を効かせます。これで、通常のAPI検証は署名だけで完結してスケールを保ちつつ、更新のタイミングで失効を反映できます。「完全ステートレス」と「即時失効」は両立しないため、トレードオフを引き受けた設計にします。
認証は正しく実装されているのに、あるAPI GET /api/invoices/{id} で、ログイン済みなら他人の請求書も取得できてしまうことが判明しました。この脆弱性の名前と根本原因、そして修正方針を説明してください。
解答を見る
これはIDOR(Insecure Direct Object Reference)/ BOLA(Broken Object Level Authorization)です。根本原因は、認証(ログイン)は通っているものの、「その請求書がリクエスト元ユーザーのものか」というオブジェクト単位の認可チェックが欠けていることです。修正方針は、{id} の請求書を返す前に、サーバー側で「その請求書の所有者 == 認証済みユーザー(または閲覧権限を持つか)」を必ず検証すること。クライアントから来たIDを信用して直接引かず、認可を通してから返します。認証と認可は別物であり、認証済みであることは何かを許す理由にはなりません。