独立サイトのAPIセキュリティ:認証、レート制限、CORS、入力検証、脆弱性対策
1. なぜ「HTTPSにした」だけではAPIは安全にならないのか
多くのサイト運営者はHTTPSを安全の代名詞ととらえ、ブラウザとサーバー間が暗号化されていればAPIは安心だと考えます。これが最初の認識ミスです。HTTPSが解決するのは「通信路上で盗み聞きされる」ことで、「正規の身元を悪用して連打される」「悪意あるスクリプトでログインAPIを大量に叩かれる」「リクエストにSQLを仕込まれてテーブルを抜かれる」ことは解決できません。
ここでの直感に反する判断はこうです。実際に独立サイトで事故が起きる原因は、ほぼすべてアプリケーション層の脆弱性であり、トランスポート層ではありません。データベースの抜き取り、削除、APIの連打によるダウンは、九割が入力未検証、認証の甘さ、レート制限の未設定に起因します。WAFは既知の攻撃パターンの一部は防げますが、自社のビジネスロジックに生じた穴は防げません。これはサイトが攻撃を受けた後の振り返り結論と一致します。まずコードを整理し、その後に強化を考えるべきです。
五層の防御はそれぞれ役割が異なり、一つ欠けるとその種類の攻撃が直接貫通します。次の表で、各層が「何を防ぎ、欠けるとどうなるか」を一度に整理します。
| 防御層 | 防ぐ攻撃 | 典型的な実装 | 漏れたときの結果 |
|---|---|---|---|
| 認証 | 匿名呼び出し、他人の身元のなりすまし | APIキー、JWT、OAuth2 | データAPIを誰でも直接取得できてしまう |
| レート制限 | ブルートフォース、APIの大量連打 | IP/キー単位のスライディングウィンドウ | ログインAPIへのクレデンシャルスタッフィング、データベースの抜き取り |
| CORS | 悪意あるサイトからのクロスドメインデータ読み取り | オリジンホワイトリスト | ユーザーのログイン状態で第三者サイトにデータを読まれる |
| 入力検証 | SQLインジェクション、XSS、不正データの書き込み | ホワイトリスト検証+パラメータ化クエリ | データベース全体の抜き取り、管理画面へのXSSによるセッションハイジャック |
| 脆弱性スキャン | 既知の裏口、古い依存関係 | 依存関係スキャン+定期的な脆弱性診断 | 公開EXPのある古いプラグインを使っている |
2. 認証:三つの方式の選び方と、鍵の管理方法
独立サイトの外部APIは二種類に分かれます。フロントエンドのブラウザが使うもの(ログイン状態、フォーム送信)と、外部システムとの連携に使うもの(Webhook、サードパーティ同期)です。この二種類では使う認証方式が異なるので、混同しないでください。
フロントエンドのログイン状態にはJWTを使います。トークンに有効期限を持たせ、デフォルトは2時間。期限切れはrefresh tokenで更新します。外部システムとの連携には長期のAPI Keyを使い、URLパラメータではなくリクエストヘッダーに入れます。URLはログやブラウザ履歴に残るためです。OAuth2は「ユーザーが第三者に自分の代理アクセスを許可する」必要がある場合だけ導入します。個人の独立サイトでは90%のケースで不要であり、自分で複雑さを増やすだけです。
判断基準はこうです。そのAPIが自分のフロントエンド専用ならJWTで十分です。取引先のプログラムから呼ばれる場合は独立したAPI Keyを発行し、取引先ごとにKeyを分けて作成します。そうすればいつでも個別に失効できます。鍵は四半期ごとに1回ローテーションし、切り替え時は新旧のKeyを7日間併存させ、連携先が一気に止まらないようにします。
| 認証方式 | 適用シーン | トークン有効期限 | ローテーション / 失効 |
|---|---|---|---|
| セッションCookie | 従来型のフォームログイン管理画面 | ブラウザセッション | ログアウトで失効 |
| JWT | フロントとバックエンドを分離、フロントがAPIを呼ぶ | アクセストークン2時間 | refreshは7日、サーバー側でブラックリスト管理 |
| APIキー | 外部システム、Webhookコールバック | 長期、取引先ごとに発行 | 四半期ごとにローテーション、個別失効可 |
| OAuth2 | 第三者アプリがユーザーに代わって認可 | 短期access + refresh | ユーザーがいつでも認可を取り消せる |
三、レート制限:誰を単位に制限し、しきい値をいくつにするか
レート制限で最も多い失敗は「サイト全体で1つのしきい値」にすることです。ログインAPIと商品一覧APIでは正常時の呼び出し頻度が桁違いなので、同じしきい値では実際のユーザーを誤って制限するか、攻撃を止められないかのどちらかになります。正しいやり方は、APIごとにレベル分けし、単位ごとに制限することです。
レート制限は3つの単位を重ねます。IP単位で単独の攻撃者が帯域を使い切るのを防ぎ、アカウント単位で漏洩したパスワードリストの総当たりを防ぎ、API Key単位で特定の取引先プログラムの暴走を防ぎます。しきい値は具体的な数字で示します。次の表は独立サイトでよくあるAPIの初期値で、公開後に実際のトラフィックを見て調整します。
シナリオ例:ログインAPIへのパスワードリスト攻撃。IP単位で毎分10回、アカウント単位で毎分5回の失敗に設定し、5分間連続で超過したらそのIPを1時間ブロックします。これは単に「ログイン失敗5回でアカウントロック」とするより効果的です。攻撃者は同じパスワードで別のアカウントを試すため、単一アカウントのしきい値では防げないからです。フォーム関連のスパム対策は、問い合わせフォームの信頼性のスパム対策を参考にしてください。
| API種別 | 制限単位 | 初期しきい値 | 超過時の動作 |
|---|---|---|---|
| ログイン / パスワード再設定 | IP + アカウント | 10回/分 | 1時間の一時ブロック |
| 問い合わせフォーム送信 | IP | 3回/分 | CAPTCHAを表示 |
| 公開商品一覧 | IP | 60回/分 | 429を返す |
| 外部API連携 | APIキー | 契約で定めたQPS | 429 + 連携先に通知 |
| サイト内検索 | セッション | 20回/分 | 429を返す |
四、CORS:いつ有効にすべきか、どこまで許可するか
CORS(クロスオリジンリソース共有)はブラウザが備えている保護機能です。既定では、あなたのページのスクリプトは他ドメインのAPIの戻り値を読めません。これは「有効にすると危険」ではなく、「設定を間違えると危険」なのです。最も多いミスは、手間を省いて Access-Control-Allow-Origin: * と書き、さらに Allow-Credentials: true を組み合わせるケースです。これはクロスオリジンの入口をネット全体に開放するのと同じです。
判断のルールはこうです。あなたのAPIを自社のフロントエンドだけが呼ぶなら、CORSの許可リストには自社ドメインだけを入れてください(wwwありとwwwなしの両方)。* は加えません。第三者のフロントエンドが呼ぶ場合は、ドメインを1つずつ追加します。*.yourdomain.com のような正規表現によるあいまいな書き方は避けてください。サブドメインが取得されたり攻撃されたりした時点で、そこが攻撃対象になってしまいます。
Cookieを使わない純粋な公開API(商品一覧など)は、そもそもCORSの認証情報を有効にする必要がありません。ブラウザのシンプルリクエストへの制限はそこまで厳しくないので、設定を1つ減らせばリスクも1つ減ります。認証情報とクロスオリジンデータのコンプライアンスの境界線は、次を参照してください。海外展開サイトのコンプライアンス基盤Cookieとデータの越境に関する説明です。
| シナリオ | Allow-Origin の設定 | Allow-Credentials | 安全性 |
|---|---|---|---|
| 自社フロントエンドのみが呼ぶ | 許可リストを自社ドメインに限定 | true(ログイン状態が必要) | 安全性 |
| 公開の商品データAPI | 設定しない、または * | false | 安全性 |
| 手間を省いて * と認証情報 true を書く | * | true | 危険。必ず修正 |
| 正規表現 *.yourdomain.com | あいまいマッチ | true | 危険。サブドメインを攻撃された時点で突破される |
| 複数の提携先フロントエンド | ドメインを1つずつ許可リストに追加 | true | 安全。不要な項目は定期的に削除 |
五、入力検証と脆弱性対策:不正なデータを入口で防ぐ
入力検証は、この5層のうち一人チームが最も見落としやすく、影響が最も重い層です。原則はただ一つ。フロントエンドや外部APIから来るデータは決して信用しないこと。すべての入力値に許可リスト方式の検証を行います。型、長さ、形式、列挙値が合わなければそのまま拒否し、「サニタイズしてから使い続ける」ことはしないでください。
SQLインジェクションはパラメータ化クエリで根本から防ぎ、SQL文字列を自分で連結しないでください。XSSは出力時のエスケープで根本から防ぎます。ユーザーが送信した内容はデータベースに保存する時点ではエスケープせず、ページに出力する時点でコンテキストに応じてエスケープします(HTMLコンテキスト、JSコンテキスト、URLコンテキストではエスケープ規則が異なります)。ファイルアップロードは拡張子の許可リストを設け、ファイル名を変更し、Webディレクトリ以外に保存します。これらはOWASP Top 10の基本項目であり、何ら「高度なテクニック」はありません。
脆弱性スキャンは決まったリズムで実施します。依存関係のロックファイルは毎週自動でスキャンし、高危険度(CVEスコア9以上)が見つかれば72時間以内にアップグレードするか、その機能を一時停止します。サイト全体は毎月1回パッシブスキャンを行い、結果と照らし合わせて期限切れのプラグインを整理します。監視アラートの連携方法は、次を参照してください。サイト監視とアラート。技術スタック自体のセキュリティ上のトレードオフは、次と照らし合わせてください。環形山テックスタック。
| 対策項目 | やり方 | 頻度 / しきい値 | レッドライン |
|---|---|---|---|
| 入力値の検証 | 型+長さ+許可リストの列挙値 | すべてのAPIで必須 | フロントエンドだけでの検証は禁止 |
| SQLインジェクション | パラメータ化クエリ / プリコンパイル | コードレビュー必須確認項目 | SQL文字列連結の禁止 |
| XSS | 出力は文脈に応じてエスケープ | すべてのユーザー出力箇所 | リッチテキストを未サニタイズで raw 出力するのを禁止 |
| ファイルアップロード | 拡張子ホワイトリスト+リネーム | アップロードAPIに必須設定 | アップロードディレクトリでのスクリプト実行を禁止 |
| 依存関係の脆弱性スキャン | ロックファイルを自動スキャン | 週に一度 | 深刻度9以上の高危険度は72時間以内に修正 |
FAQ よくある質問
自社サイトはWAFを入れないと安全とは言えないのか?
JWTとAPIキーはどちらを使うべき?
レート制限のしきい値はどう決めれば正常なユーザーを誤って弾かない?
CORSで Access-Control-Allow-Origin: * と書くと何が問題?
入力検証をフロントエンドでやっているのになぜバックエンドでも必要なのか?
脆弱性スキャンはどのくらいの頻度でやればいい?
最後に
独立系サイトのAPIセキュリティの本質は、ツールを積み上げることではなく層に分けることです。どこかの層が突破されても、次の層が食い止めます。単一の防御が最も脆く、WAFをどれだけ立派に設定しても入力検証がなければ、インジェクションはそのままデータベースに届きます。5層のうち優先すべきは入力検証と認証で、この2層は投資が最も小さく効果が最も大きいです。レート制限とCORSは設定作業で半日あれば整い、脆弱性スキャンは週1回の自動実行タスクにします。
実装は3ステップです。第一に、すべての外部インターフェースに認証を追加し、長期キーは取引先ごとに分けて発行します。第二に、ログインとフォームのインターフェースに段階的なレート制限を設定し、公開インターフェースはまず緩めにします。第三に、各入力パラメータをバックエンドでホワイトリスト検証し、依存関係スキャンを週次タスクに組み込みます。この3ステップを終えれば、あなたのインターフェースは「たまに試される」状態になり、「門戸を開け放した」状態ではなくなります。日々のメンテナンスのペースはこちらを参照してください。公式サイト保守期間一緒に並べよう。
覚えておきたい一言。APIセキュリティは一枚の壁ではなく、五つの扉です。どの扉も鍵をかけなければならず、一つでも漏れれば残りの四つは形だけになります。
