目次
- 基本概念
- 認証方式の選び方
- OAuth 2.0 / OpenID Connect
- トークン設計(JWT など)
- API キー
- 認可(アクセス制御)
- 通信とクレデンシャルの保護
- サービス間認証
- 防御・運用
- よくあるアンチパターン
- チェックリスト
- 参考資料
1. 基本概念
| 用語 | 意味 | 問い |
|---|
| 認証(Authentication / AuthN) | 呼び出し元が誰かを確認する | 「あなたは誰?」 |
| 認可(Authorization / AuthZ) | その主体が操作してよいかを判断する | 「それをしてよい?」 |
- 認証と認可は別のレイヤーとして設計する。「ログインしている=何でもできる」にしない。
- 認可はリクエストごと・リソースごとに評価する。
2. 認証方式の選び方
| ユースケース | 推奨方式 |
|---|
| ブラウザ SPA / モバイルアプリからの API 呼び出し | OAuth 2.0 認可コードフロー + PKCE |
| ユーザーのログイン(ID 連携) | OpenID Connect(OIDC) |
| サーバー間(マシン間)通信 | OAuth 2.0 Client Credentials、mTLS、ワークロード ID |
| 外部開発者向けの公開 API | OAuth 2.0(ユーザーデータ扱い時)または API キー(公開データ・利用量管理用) |
| 同一ドメインの従来型 Web アプリ | サーバーサイドセッション + Cookie(HttpOnly / Secure / SameSite) |
| 社内のサービスメッシュ | mTLS + SPIFFE などのワークロード ID |
原則: 独自の認証プロトコルを発明しない。実績ある標準と、メンテされているライブラリ / IdP を使う。
3. OAuth 2.0 / OpenID Connect
推奨フロー
- 認可コードフロー + PKCE を標準にする。 パブリッククライアント(SPA・モバイル)だけでなく、コンフィデンシャルクライアントでも PKCE を使う。
- PKCE の
code_challenge_method は S256 を使う(plain は使わない)。
- ブラウザアプリでは BFF(Backend for Frontend)パターン を検討する。トークンをサーバー側で保持し、ブラウザには
HttpOnly Cookie のセッションだけを渡すことで、XSS によるトークン窃取リスクを下げられる。
使ってはいけないフロー
| フロー | 理由 |
|---|
| Implicit Grant | アクセストークンが URL フラグメントに露出する。OAuth 2.0 Security BCP(RFC 9700)で非推奨 |
| Resource Owner Password Credentials | クライアントがユーザーのパスワードを直接扱う。同上で非推奨 |
実装上の注意
redirect_uri は完全一致で検証する(ワイルドカードや前方一致は使わない)。
state パラメータ(または PKCE)で CSRF を防ぐ。OIDC では nonce も検証する。
- スコープは最小限に絞り、機能単位で細かく定義する(例:
orders:read, orders:write)。
- 認可サーバーのメタデータ(
/.well-known/openid-configuration)から設定を取得し、ハードコードを避ける。
- OAuth 2.1(策定中のドラフト)は上記のベストプラクティスを取り込んだ仕様なので、新規実装はこれに沿っておくと将来の移行が楽になる。
4. トークン設計(JWT など)
アクセストークン
- 有効期限を短くする(目安: 5〜15 分程度)。
- 用途(audience)を限定し、API 側で
aud を必ず検証する。
- トークンには必要最小限のクレームのみを含める。個人情報や機密情報を入れない(JWT は署名されていても暗号化はされていない)。
JWT の検証で必ず確認すること
| 項目 | 内容 |
|---|
| 署名 | 期待するアルゴリズムをサーバー側で固定(ヘッダーの alg を信用しない)。alg: none は拒否 |
iss | 信頼する発行者か |
aud | 自分の API 宛てか |
exp / nbf / iat | 有効期間内か(数十秒程度のクロックスキューを許容) |
kid | JWKS から鍵を取得。JWKS はキャッシュしつつ、ローテーションに追従する |
- 署名アルゴリズムは非対称鍵(
RS256, ES256, EdDSA など)を推奨。複数サービスで検証する場合、共有秘密(HS256)は秘密の漏洩範囲が広がる。
リフレッシュトークン
- ローテーション(使用のたびに新しいリフレッシュトークンを発行し、古いものを無効化)を有効にする。
- 再利用(リプレイ)を検知したら、そのトークンファミリー全体を失効させる。
- 絶対有効期限とアイドルタイムアウトを設定する。
- 可能であれば 送信者制約付きトークン(DPoP や mTLS バインディング)で、盗まれたトークンを別端末で使えないようにする。
失効(Revocation)
- JWT は自己完結型のため即時失効が難しい。対策として:
- アクセストークンの有効期限を短くする
- 高リスク操作では Token Introspection(RFC 7662)で都度確認する
- 必要に応じて失効リスト(
jti ベース)を持つ
- ログアウト・パスワード変更・権限変更時にリフレッシュトークンを失効させる。
クライアント側の保管
| 保管場所 | 評価 |
|---|
HttpOnly + Secure + SameSite Cookie(BFF 経由) | 推奨 |
| メモリ(JS 変数) | 許容。リロードで消える前提で設計 |
localStorage / sessionStorage | 非推奨。XSS で容易に読み取られる |
| モバイル | OS のセキュアストレージ(iOS Keychain / Android Keystore) |
5. API キー
API キーは「アプリケーションの識別」には有効だが、ユーザーの認証手段としては不十分。
- 十分なエントロピーを持つランダム値で生成する(例: 32 バイト以上)。
- サーバー側にはハッシュ化して保存する(平文で保存しない)。
- 識別しやすいプレフィックスを付ける(例:
sk_live_...)。漏洩検知(シークレットスキャン)に引っかかりやすくなる。
- キーごとにスコープ・IP 制限・レート制限を設定できるようにする。
- ローテーションを前提に設計する(新旧キーの並行稼働期間を設ける)。
- URL のクエリパラメータではなく
Authorization ヘッダーで送る(クエリはログやリファラーに残る)。
- クライアントサイド(ブラウザ・モバイルアプリのバイナリ)に秘密のキーを埋め込まない。
6. 認可(アクセス制御)
基本原則
- デフォルト拒否(Deny by default): 明示的に許可されていないものは拒否する。
- 最小権限の原則: 必要な権限だけを付与する。
- サーバー側で強制: クライアント側の UI 制御はセキュリティ対策ではない。
- 一元化: 認可ロジックをハンドラーごとに散在させず、ミドルウェアやポリシーエンジンに集約する。
オブジェクトレベル認可(BOLA / IDOR 対策)
OWASP API Security Top 10 で最上位のリスク。
GET /api/orders/12345
- 「認証済みであること」だけでなく、「このユーザーがこの注文 12345 にアクセスしてよいか」を毎回確認する。
- データ取得時に所有者条件を含める(例:
WHERE id = ? AND tenant_id = ?)。
- 推測困難な ID(UUID など)を使うのは補助策であり、認可チェックの代わりにはならない。
機能レベル・プロパティレベル認可
- 管理者用エンドポイントを「URL が知られていないから安全」としない(BFLA 対策)。
- レスポンスで返すフィールド、更新を許可するフィールドを明示的に制御する(Mass Assignment / 過剰なデータ露出の防止)。
- 例: 一般ユーザーが
PATCH /users/me で role や is_admin を変更できないようにする。
認可モデルの選択
| モデル | 特徴 | 向いているケース |
|---|
| RBAC(ロールベース) | ロールに権限を紐付け | 権限体系が比較的単純 |
| ABAC(属性ベース) | ユーザー・リソース・環境の属性で判断 | 部署・時間帯・地域など条件が多い |
| ReBAC(関係ベース) | リソース間の関係で判断(Google Zanzibar 型) | ドキュメント共有、組織階層など |
- 複雑なポリシーは OPA(Open Policy Agent)、Cedar、OpenFGA などのポリシーエンジンの利用を検討する。
マルチテナント
- すべてのクエリにテナント境界の条件を強制する(ORM のグローバルスコープ、DB の行レベルセキュリティなど)。
- トークン内のテナント ID と、リクエスト対象リソースのテナント ID を照合する。
7. 通信とクレデンシャルの保護
- TLS を必須にする(TLS 1.2 以上、可能なら 1.3)。HTTP は拒否、HSTS を設定する。
- クレデンシャルは
Authorization ヘッダーで送る。URL に含めない。
- ログ・エラーメッセージ・トレースにトークンやパスワードを出力しない(マスキングする)。
- シークレットはソースコードや環境設定ファイルにハードコードせず、シークレットマネージャー(AWS Secrets Manager、GCP Secret Manager、HashiCorp Vault など)で管理する。
- CI にシークレットスキャン(gitleaks、GitHub Secret Scanning など)を組み込む。
- 署名鍵は定期的にローテーションし、JWKS で新旧の鍵を並行公開する。
8. サービス間認証
- 「社内ネットワークだから信頼する」をやめ、ゼロトラストを前提にする。
- 選択肢:
- mTLS: 双方向の証明書認証。サービスメッシュ(Istio、Linkerd など)で自動化しやすい。
- OAuth 2.0 Client Credentials: サービスごとにクライアント ID と最小スコープを付与。
- ワークロード ID: クラウドのマネージド ID(AWS IAM Role、GCP Workload Identity、Azure Managed Identity)や SPIFFE/SPIRE を使い、長期のシークレットを持たない。
- ユーザーの代理で下流サービスを呼ぶ場合は、ユーザーのトークンをそのまま転送せず、Token Exchange(RFC 8693) で下流用の権限を絞ったトークンを取得する。
9. 防御・運用
ブルートフォース・不正利用対策
- ログイン・トークン発行エンドポイントにレート制限をかける(IP 単位とアカウント単位の両方)。
- 認証失敗時のメッセージは汎用的にする(「ユーザーが存在しません」と「パスワードが違います」を区別しない)。
- 重要操作や管理者アカウントには MFA を必須にする。フィッシング耐性のある方式(パスキー / WebAuthn)を優先する。
- パスワードを保存する場合は Argon2id、bcrypt、scrypt などの低速ハッシュを使う。
ステータスコード
| 状況 | コード |
|---|
| 認証情報がない・無効 | 401 Unauthorized(WWW-Authenticate ヘッダーを付与) |
| 認証済みだが権限がない | 403 Forbidden |
| リソースの存在自体を隠したい | 404 Not Found |
監査とモニタリング
- 認証の成功 / 失敗、トークン発行、権限変更、認可拒否を監査ログに記録する(誰が・いつ・何に・結果)。
- 異常検知: 短時間の大量失敗、通常と異なる地域・端末からのアクセス、リフレッシュトークンの再利用など。
- ログ自体へのアクセスも制御し、改ざんを防ぐ。
テスト
- 認可のテストを自動化する。特に「ユーザー A がユーザー B のリソースにアクセスできないこと」を明示的にテストする。
- 期限切れ・署名不正・
aud 不一致・スコープ不足のトークンが拒否されることをテストする。
- 定期的にペネトレーションテストや DAST を実施する。
10. よくあるアンチパターン
| アンチパターン | 問題点 | 対策 |
|---|
| 独自の暗号・トークン形式を実装 | 脆弱性を作り込みやすい | 標準仕様と実績あるライブラリを使う |
JWT の alg をヘッダーから信用 | none や鍵混同攻撃を受ける | 許可アルゴリズムをサーバー側で固定 |
| 有効期限なし / 非常に長いアクセストークン | 漏洩時の被害が長期化 | 短命トークン + リフレッシュ |
トークンを localStorage に保存 | XSS で窃取される | BFF + HttpOnly Cookie |
| ID が推測困難だから認可チェック不要 | BOLA 脆弱性 | 毎回所有者・権限を確認 |
クライアントから送られた role / user_id を信用 | 権限昇格 | 検証済みトークンからのみ主体を取得 |
| API キーをフロントエンドに埋め込む | 誰でも取得できる | サーバー側で保持・プロキシする |
| トークンや API キーをログに出力 | ログ経由で漏洩 | マスキング |
| 内部 API は認証なし | 侵入後の横展開を許す | mTLS / ワークロード ID |
11. チェックリスト
認証
認可
運用
12. 参考資料