代表的な面接トピック

バックエンド面接:セキュアなAPIキー認証をどのように設計するか?

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

マルチテナントB2Bプラットフォームがサーバー間(Server-to-Server)APIを公開しています。顧客には、本番環境とテスト環境のキーの分離、スコープ付き権限、クォータ、有効期限、ダウンタイムなしのローテーション、漏洩直後の即時失効が必要です。APIキー認証システムを設計し、そのセキュリティ境界を説明してください。

課題と適用シナリオ

マルチテナントB2Bプラットフォームがサーバー間APIを公開しています。各顧客は異なる連携のために複数のキーを作成できます。システムには、本番環境とテスト環境の分離、スコープ付き権限、キー単位およびテナント単位のクォータ、任意の有効期限、ダウンタイムなしのローテーション、侵害後の迅速な失効が必要です。

発行、保存、検証、認可、ローテーション、失効、監査の各パスを設計してください。また、どのような場合にAPIキーが不適切な認証情報となるかを説明してください。クライアントはシークレットを保護できる信頼されたサーバーであり、ブラウザやモバイルアプリケーションはこの認証情報モデルの対象外です。

この設計では、次の5つの不変条件を維持する必要があります。

  1. 完全なシークレットは1回のみ表示され、平文で保存またはログ記録されることは決してない。
  2. 有効なキーはマシン主体(machine principal)を識別するが、すべてのアクションを自動的に認可するわけではない。
  3. キーは厳密に1つのテナントと1つの環境に属する。
  4. 失効は、定義されテスト可能な伝播目標時間内に有効になる。
  5. ローテーションでは、どのアカウント情報がリクエストを行ったかを隠すことなく、古いキーと新しいキーを重複して使用できる。

面接官が評価するポイント

最初の評価基準は、候補者が認証と認可を分離しているかどうかです。シークレットを検証することで、どのAPIキー主体がリクエストを送信したかが確定します。サービスは依然として、スコープ、エンドポイントポリシー、リソースの所有権、テナントの分離を適用しなければなりません。

2つ目は、シークレットの扱いです。優れた回答では、暗号学的に安全な乱数生成器を使用して高エントロピーの不透明(opaque)なシークレットを生成し、1回だけ表示し、ベリファイア(検証用データ)のみを保存し、あらゆる場所でマスキングし、サーバー側のペッパー(pepper)を専用のシークレットマネージャーに保持します。

3つ目は、ライフサイクルの設計です。キーには、名前、環境、ステータス、有効期限、スコープ、帰属情報、ローテーション、失効が必要です。キーを単一の恒久的なデータベース文字列として扱うと、運用者は危険な共有や業務停止を伴う差し替えを余儀なくされます。

4つ目は、運用上の推論力です。キャッシュ、レート制限、ログ、インシデント対応、可用性はすべてセキュリティ境界に影響します。エッジキャッシュが失効したキーを10分間受け入れる場合、「即時失効」は成り立ちません。

最後に、信頼できないクライアントや十分に機密性の高い操作に対して、APIキーの利用を適切に却下できるかどうかも評価されます。フロントエンドコードにコピーされた静的なBearerシークレットは、ユーザーや攻撃者によって復元可能です。価値の高いワークフローやユーザー委任ワークフローには、短命なワークロード認証情報、OAuth、相互TLS(mTLS)、リクエスト署名、またはステップアップ制御が必要になる場合があります。

回答前の明確化のための質問

  • 誰が認証情報を保持するのか? バックエンドサービスはシークレットを保護できますが、ブラウザ、モバイルアプリ、デスクトップバイナリ、パブリックリポジトリでは機密性を保証できません。
  • キーは何を表すのか? テナント連携、内部ワークロード、または人間のいずれを表すのかを定義します。この設計では、エンドユーザーセッションではなく、テナントが所有するマシン主体を使用します。
  • 操作の機密性はどの程度か? 読み取り専用のアナリティクスと資金移動では、同一の制御を適用すべきではありません。
  • 失効と可用性の目標値はどの程度か? 測定可能な伝播目標を選択し、プライマリキーストアが利用できない場合に認証がどのように動作するかを決定します。
  • 環境はどのように分離されているか? テスト用キーと本番用キーには、個別のプレフィックス、データ境界、権限、クォータが必要です。
  • 権限はどの程度きめ細かく設定されるか? 大まかなスコープにリソースレベルの認可を組み合わせます。プロダクトで必要とされない限り、無制限のカスタムポリシー言語は避けます。
  • テナントはいくつのキーを作成できるか? 上限を設けることで、キーの乱立を防ぎ、無制限に認証情報を発行してキー単位のクォータを回避する行為を阻止します。
  • どのような監査およびコンプライアンスルールが適用されるか? 保持期間、作成者の帰属、最終使用日時データ、承認フロー、緊急アクセスなどが規制される場合があります。

30秒の回答フレームワーク

「私は、ak_live_7F3KQ2.m8…Vw のように、検索可能なパブリックIDと不透明なランダムシークレットを含むキーを発行します。完全な値は1回だけ返されます。データベースには、パブリックID、テナント、環境、スコープ、ステータス、有効期限、およびキー付きベリファイア(keyed verifier)が保存され、平文のシークレットは保存されません。

各TLSリクエストにおいて、ゲートウェイはヘッダーからキーを抽出し、パブリックIDで行を検索し、ベリファイアを再計算して定数時間で比較し、ステータスと有効期限を確認します。その後、マシン主体のコンテキストを作成します。エンドポイントのスコープとテナントの所有権は個別に検証されます。レート制限はキーとテナントの両方に適用され、監査ログにはパブリックIDのみが含まれます。

ローテーションを行う場合は、2つ目のキーを作成してデプロイし、両方のIDの使用状況を観察した上で、古いキーを失効させます。漏洩が発生した場合は、猶予期間なしで即時失効、ログレビュー、差し替えを実行します。失効により、宣言された目標時間内にキャッシュが無効化されます。パブリッククライアント、ユーザー委任、高価値のアクションには、より強力な認証情報または短命な認証情報を使用します。」

ステップごとの詳細解説

ステップ1:IDとキーレコードのモデル化

各キーを個別のマシン主体として扱います。すべての連携でテナント共通の単一のシークレットを共有してはなりません。実用的なレコードは次のとおりです。

text
ApiKey(
  key_id, tenant_id, environment, name, verifier,
  verifier_version, scopes, status, expires_at,
  created_at, created_by, last_used_at
)

key_id は公開され、検索可能です。name は、運用者が『請求データのエクスポート』と『倉庫の同期』を区別するのに役立ちます。status は少なくとも有効(active)と失効(revoked)の状態をサポートし、有効期限は個別に評価されます。created_by と概算の last_used_at により帰属情報が向上します。すべてのリクエストで同期的に last_used_at を更新しないでください。書き込みのホットスポットが発生するためです。分単位の精度で十分な場合は、非同期に集計またはサンプリングします。

スコープは、invoices:read のような大まかな機能を記述します。これらはリソースの認可に代わるものではありません。キーを受け入れた後でも、請求書 123 に対するリクエストには、認証された tenant_id によって制限されたクエリが依然として必要です。呼び出し元が指定したテナントを信頼できる権限情報として受け入れてはなりません。

ステップ2:1回のみ生成、1回のみ開示、ベリファイアの保存

暗号学的に安全な乱数生成器を使用して、32バイトのランダムシークレットを生成します。これは具体的な設計上の選択であり、普遍的なプロトコルの要件ではありません。転送セーフな文字セットでエンコードし、識別可能なプレフィックスおよびパブリックIDと組み合わせます。

text
ak_live_7F3KQ2.m8...opaque-secret...Vw

プレフィックスにより、サポートツールはシークレットを公開することなく認証情報のタイプと環境を識別できます。作成成功のレスポンスでのみ、完全なキーを返します。UIでは、キーを再復元できないこと、紛失した場合は再発行が必要であることを明記する必要があります。

ベリファイアとして HMAC-SHA-256(server_pepper, complete_secret) を保存します。ペッパーはデータベースから分離されたシークレットマネージャーに保持します。十分にランダムなトークンであれば単純なSHA-256ダイジェストも有効ですが、キー付きベリファイアを採用することで、データベースのみが漏洩した場合の多層防御が追加されます。サービスがアルゴリズムやペッパーを移行できるように、ベリファイアにバージョンを付与します。ペッパーの移行では、期限付きのデュアル検証または計画的なキー再発行計画をサポートする必要があります。顧客のすべてのキーを予告なく無効化することは許容されません。

キーの作成は、認証された管理アクションです。シークレットを生成する前に、テナントのロール、キー数の上限、許可されたスコープ、環境、有効期限ポリシー、および必要な承認を適用します。レコードを保存し、決してキャッシュされないレスポンスを介してシークレットを返します。アプリケーション、プロキシ、トレーシング、エラーレポート、サポートツールの各レイヤーで、認可ヘッダーとレスポンスボディをマスキングします。

ステップ3:権限を拡大せずにリクエストを認証する

TLSを必須とし、キーは認可ヘッダーまたは専用ヘッダーで受け入れ、URLクエリ文字列では決して受け入れないようにします。URLは履歴、アナリティクス、プロキシログ、リファラーデータに頻繁に残るためです。フォーマットを分割して検証し、パブリックIDを使用してインデックス付き検索を行い、負荷の高い処理の前に不正な形式の入力を拒否します。

候補となる行に対して、ベリファイアを再計算し、定数時間比較を使用します。次に、環境、アクティブステータス、有効期限を確認します。エンドポイントがキーの列挙オラクル(推測の手がかり)にならないよう、不明、不正形式、期限切れ、失効済みのキーに対しては、同一の一般的な外部エラーを返します。内部的には、シークレットを含まない安全な理由コードを記録します。

検証に成功すると、key_idtenant_id、環境、スコープを含むコンテキストが作成されます。ルートポリシーは必要なスコープをチェックし、データレイヤーはそのテナントとリソースへのアクセスを制限します。管理エンドポイントや高価値のエンドポイントでは、APIキー主体を完全に拒否するか、追加の制御を要求することができます。

ステップ4:多層クォータと監視による不正利用の制限

レート制限は身元を証明するものではありませんが、盗難された認証情報やバグのある認証情報による被害を抑えることができます。キーごとのバースト制限と持続制限を適用し、さらにテナント全体の集約制限を適用します。テナントレイヤーの制限により、顧客が多数のキーを作成して利用枠を増殖させるのを防止します。負荷の高いエンドポイントには、個別のコスト加重バジェットや並行実行数の制限が必要になる場合があります。

パブリックキーID、テナント、ルート、判定結果、レイテンシ、ポリシーで許可された送信元ネットワークのメタデータ、リクエストの相関ID(correlation ID)を記録します。完全なキー、ベリファイア、再利用可能な認可ヘッダーは決してログに記録しないでください。通常と異なる地域やネットワークの変更、突然のエラー急増、スコープ拒否の急増、休眠キーのアクティブ化、ローテーション通知後のトラフィックに対してアラートを発報します。これらは調査の契機となるシグナルであり、侵害の自動的な証明ではありません。

キー管理エンドポイントは、通常のデータエンドポイントよりも厳格に制限します。強力な人間向け認証、Cookieベースのコンソールに対するCSRF保護、明示的な認可、監査イベント、作成数制限、場合によっては再認証や承認が必要です。

ステップ5:キャッシュと迅速な失効の整合性を取る

インデックス付きの検証検索はシンプルであり、データベースに信頼できる最新の判定を下させることができますが、リクエスト量が非常に多い場合はキャッシュが正当化される場合があります。パブリックIDの下にはベリファイアと最小限の認可メタデータのみをキャッシュし、トランスポートを暗号化し、エントリ数を制限します。提示されたシークレットは決してキャッシュしないでください。

失効処理では、最初に信頼できるステータスを書き込み、ゲートウェイに対して無効化(invalidation)を発行します。無効化が失われた場合のフォールバックとして短いTTLを設定します。プロダクト側で測定可能な目標(このシナリオでは、失効したキーは5秒以内にすべてのゲートウェイで認証を停止する)を定め、パケットロスやノード再起動の条件下でテストする必要があります。10分のTTLではその保証を維持できません。

リスクに応じて障害時の動作を選択します。機密性の高い書き込み処理では、十分に新しいキーの状態を取得できない場合はフェイルクローズ(アクセス拒否)にする必要があります。一部の低リスクな読み取り処理では、一時的に古い、以前有効だったキャッシュエントリを使用することが明示的な可用性のトレードオフとなる場合もありますが、これは厳格な即時失効に違反するため、デフォルトの動作として暗黙に組み込んではなりません。

ステップ6:異なるワークフローとしてのローテーションと失効

定期的なローテーションには移行の重複期間が必要です。

  1. 古いキーと同等以下の権限を持つ新しいキーを作成する。
  2. 顧客のシークレット管理経路を通じて新しいキーを引き渡す。
  3. 新しいキーをデプロイし、カナリアリリースを実施する。
  4. 古いキーへのアクセスがなくなるまで、パブリックキーIDごとのリクエストを監視する。
  5. 古いキーを失効させ、依存するトラフィックが残っていないことを確認する。

古いシークレットを直接上書き変更しないでください。2つの独立したIDを使用することで、移行中の帰属追跡とロールバックが可能になります。有効期限によって最大有効期間を強制できますが、導入状況のテレメトリがない強制失効は、回避可能なシステム障害を引き起こします。

漏洩が疑われる場合は、異なる順序に従います。猶予期間なしで最初に失効させ、キャッシュを無効化し、影響を受けたテナント、スコープ、ルート、時間枠を特定し、監査証拠を確認し、最小特権の代替キーを発行し、漏洩元を修正します。行を即座に削除すると、有用なインシデント証拠が消去される可能性があるため、ポリシーに従って非シークレットのメタデータを保持します。

ステップ7:APIキーが不十分な場合を把握する

APIキーはBearer(無記名)認証情報です。所持しているだけで使用できます。呼び出し元が想定されたワークロード上で実行されていることを証明するものではなく、エンドユーザーの同意を提供するものでも、それ自体でリプレイを防ぐものでもありません。シークレットキーをブラウザのJavaScript、モバイルバイナリ、サンプルコード、コンテナイメージ、リポジトリに埋め込んではなりません。

クラウドワークロードの場合は、利用可能であれば短命のワークロードIDを優先します。ユーザー委任アクセスの場合は、限定的な同意と有効期限付きトークンを備えた認可プロトコルを使用します。特に機密性の高いサービス間通信では、データベースからコピーされた値だけでは不十分となるよう、相互TLSやリクエスト署名の導入を検討します。適切な選択は脅威モデルに依存します。すべてのAPIにあらゆるメカニズムを追加することは、運用の複雑さを招くだけです。

ステップ8:セキュリティ、ライフサイクル、障害モードのテスト

以下を網羅する敵対的テストマトリクスを構築します。

  1. 不正なプレフィックス、未知のID、誤ったシークレット、定数時間でのベリファイア処理
  2. 期限切れ、失効済み、テスト用キーの本番環境利用、スコープ不足のキー
  3. 認証成功後の別テナントオブジェクトへのクロスオーバーアクセス
  4. プロキシ、アプリケーション、トレース、エラー、監査出力におけるシークレットのマスキング
  5. ローテーションの重複期間、古いキーの使用状況テレメトリ、有効期限、緊急失効
  6. 古いキャッシュエントリ、破棄された無効化イベント、ゲートウェイの再起動、キーストアの停止
  7. 1つのテナントからの多数のキー作成を含む、キー単位およびテナント単位のクォータ強制
  8. 同時作成、処理中のリクエスト実行時の失効、重複した管理API呼び出し

また、リポジトリやデプロイ設定をスキャンして、特徴的なキーのプレフィックスがないか確認します。検出ツールは復旧支援のためのものであり、ソース管理にシークレットを配置することを許可するものではありません。テスト用キーを使用してインシデント訓練を検証します。失効確認からすべてのゲートウェイでの拒否までの時間を測定し、ログがシークレットを保持せずに帰属情報を保持していることを確認します。

優れた回答例

「私は、すべてのAPIキーを1つのテナントと1つの環境によって所有される、名前付きのマシン主体として扱います。発行される値には、検索用のパブリックIDと不透明なランダムシークレットが含まれます。TLS経由で1回だけ返却し、キー付きベリファイアとライフサイクルメタデータを保存し、すべてのログ記録およびトレーシングレイヤーから完全な値をマスキングします。

ゲートウェイはパブリックIDを検索し、ベリファイアを再計算し、定数時間で比較し、環境、ステータス、有効期限をチェックします。認証されると、テナントとスコープを含むコンテキストが生成されます。各ルートは依然として自身のスコープを検証し、各データクエリはテナントの所有権を強制します。キーごとの制限により単一の連携を封じ込め、テナント制限によりクォータの増殖を阻止します。

検証メタデータがキャッシュされている場合、失効処理は信頼できる情報源(Source of Truth)を更新して無効化をプッシュし、フォールバックとして短いTTLを設けます。私は5秒の失効目標を定義し、テストします。定期的なローテーションでは、2つ目のキーを作成し、カナリアリリースを行い、両方のパブリックIDを観察してから、古いキーを失効させます。漏洩が疑われる場合は猶予期間をスキップし、失効、キーのスコープと活動時間枠の調査、より狭い権限を持つ代替キーの発行、露出元の修正を行います。

この静的なシークレットをブラウザやモバイルアプリケーションで使用したり、エンドユーザーのIDとして使用したり、高価値のアクションに対する唯一の制御として使用したりすることはありません。そうしたケースには、委任された、短命の、ワークロードにバインドされた、またはより強力な認証情報が必要です。」

よくある間違い

  • 再表示できるように平文のキーを保存する。 復元の利便性のために、データベースの読み取りが認証情報の漏洩リスクに変わります。1回のみ表示し、再発行をサポートしてください。
  • ライフサイクルメタデータを持たずにグローバルハッシュのみを使用する。 検証だけでは、テナント、環境、スコープ、有効期限、帰属情報、失効に関する問いに答えることができません。
  • クエリ文字列にキーを含める。 URLは日常的にコピーされ、ログに記録されます。TLS経由のヘッダーを使用してください。
  • スコープをテナントの認可として扱う。 invoices:read は、請求書 123 が認証されたテナントに属していることを証明しません。
  • すべての連携に1つのテナント共通キーを付与する。 漏洩時の被害半径が拡大し、帰属の追跡が曖昧になります。
  • キー単位でのみレート制限を行う。 テナントが複数のキーを作成またはローテーションし、許可されたトラフィックを増殖させることができます。
  • 数分間キャッシュしているにもかかわらず即時失効を謳う。 伝播目標を明記し、能動的に無効化し、キャッシュ障害をテストしてください。
  • シークレットを上書きしてローテーションする。 これにより、重複期間、帰属追跡、カナリアリリース、クリーンなロールバックパスが失われます。
  • パブリッククライアントで静的シークレットを使用する。 難読化によって機密性の高いストレージ境界を作成することはできません。
  • 認証のデバッグのために認証情報をログ出力する。 パブリックIDと安全な理由コードを記録してください。再利用可能なシークレットは決して記録してはなりません。

フォローアップの質問と回答

キー全体をハッシュ化してすべての行をスキャンするのではなく、パブリックIDとシークレットを使用するのはなぜですか?

パブリックIDにより、インデックス付きの検索、サポート用の安全な識別子、有用な帰属情報が得られます。シークレットは証明として機能します。すべてのベリファイアをスキャンするのは低速であり、危険なログ出力やセカンダリインデックスの使用を招きます。パブリックIDは意図的に非公開ではないため、これを知っていてもシークレットの導出に役立ってはなりません。

高速なハッシュはAPIキーベリファイアとして安全ですか?

シークレットが十分な暗号学的ランダム性を備えている場合は安全です。人間のパスワードとは異なり、推測可能な辞書から取得されたものではないためです。独立したペッパーなしでデータベースが漏洩した場合、キー付きHMACが保護層を追加します。人間が選択したパスワードには依然としてパスワードハッシュが適しており、脅威モデルが異なります。

サーバー側のペッパーはどのようにローテーションしますか?

各行にベリファイアのバージョンを保存します。期間を定めた移行期間中、ベリファイアは古いペッパーまたは新しいペッパーを選択でき、提示されたシークレットがメモリ上で利用可能な場合は、認証に成功した旧バージョンのキーを新しいバージョンで再検証できます。もう1つの選択肢は、計画的な顧客キーの再発行です。依存するすべてのベリファイアが移行または期限切れになる前に、古いペッパーを削除してはなりません。

last_used_at は正確である必要がありますか?

通常はその必要はありません。リクエストごとに1つの行を更新すると、書き込み負荷と競合が増加します。サンプリングまたは集計された使用状況イベントを送信し、定期的に更新します。セキュリティ監査イベントはリクエストレイヤーで追記専用(append-only)のまま保持し、管理UIでは last_used_at を概算値として表記します。

処理中のリクエストと失効処理が競合した場合はどのように処理しますか?

境界を明示的に定義します。認証処理では、伝播開始後に始まったリクエストを確実に拒否できます。機密性の高い操作では、コミット前にもう一度認可を再チェックするか、認証されたキーのステータスをトランザクション対応のポリシーにバインドします。失効によって、すでにコミットされた操作を遡及して消去することはできないため、インシデント対応でその時間枠を調査する必要があります。

キーは自動的に期限切れにすべきですか?

有効期限を設定することで無期限の露出は制限されますが、ローテーションや失効の代わりにはなりません。プラットフォームは所有者に通知し、古いキーの使用状況を開示し、安全な重複期間を許可し、期限後に拒否する必要があります。適切な最大有効期間は、リスクおよび、より優れた短命の認証情報が利用可能かどうかによって決まります。

IPホワイトリスト(許可リスト)はキーの盗難を解決しますか?

これは、安定した送信元アドレスを持つ顧客向けのオプションの追加制限です。シークレット検証の代わりにはならず、ネットワークの変更やプロキシの共有時に停止や誤った安心感を生む可能性があります。アイデンティティの根本ではなく、1つのシグナルまたはポリシーレイヤーとして扱ってください。

キー作成レスポンスには何を含めるべきですか?

完全なキーを1回のみ返し、そのパブリックIDまたはプレフィックス、名前、環境、スコープ、有効期限、作成メタデータを返します。レスポンスをキャッシュ不可としてマークし、ベリファイアやペッパーは決して返しません。その後のリスト一覧APIでは、パブリック識別子とメタデータのみを返します。

公開情報ソース

関連する質問