--- title: "API キーをサーバー側に移しても終わらない。誰でも叩ける窓口にレート制限と上限を" description: "API キーをサーバー側に移せば安全? 半分だけ本当です。鍵は見えなくなりますが、それを使う窓口は誰でも叩けます。隠すことと使わせすぎないことは別の仕事。レート制限(429)・使う人の確認・上限額・受ける形と返す形を、請求が跳ねる前に決める手順。" url: https://ryuuneko.com/blog/api-key-behind-server-not-enough published: 2026-09-12 20:46:41.92526 updated: 2026-09-12 20:47:36.757366 --- 「API キーはサーバー側に移したから、もう大丈夫」。半分だけ本当です。 鍵は見えなくなります。でも、その鍵を使う窓口は開いたままで、誰でも叩けます。**隠すことと、使わせすぎないことは別の仕事**です。何回まで、誰に許すかを、自分で決める必要があります。 この記事は、[.env の鍵がブラウザから見えていた話](/blog/env-file-not-secret-vite) の続きで、鍵をサーバー側に移したあとに残っていた宿題を、架空の例で**再現した記録**です。ログの内容や回数は架空で、金額やサービス名は出しません。429 の扱いは RFC 6585、対策の並びは OWASP API Security Top 10(2023)で確認しています。 --- ## ログを見たら、知らないところから来ていた 鍵をサーバーに移した誤字さがしの窓口を、友達が使っています。出す前に入れたのは回数の歯止めだけ。まだ請求が跳ねる前に、アクセスログを開きます。 ``` 09:12 POST /api/typos ← 友達 09:13 POST /api/typos ← 友達 14:40 POST /api/typos 知らないところから 14:41 POST /api/typos 知らないところから ``` まだ実害は出ていません。ただ、友達以外からの呼び出しが混じっている。叩きすぎは断っているはず。断ってはいる。でも、呼べること自体は止めていない。 ブラウザ側の通信を見直しても、鍵は載っていません。前回直したとおりです。 ``` POST https://tidy-text.example/api/typos content-type: application/json (Authorization ヘッダは無い) ``` 問題はこの住所です。誰でも打てる。ここに文を投げれば、うちの鍵で誤字さがしが動く。鍵が無くても、使えてしまう。鍵は隠れている。回数も断っている。でも、**使っていい人を決めていない**。 --- ## なぜ窓口だけ開くのか 鍵を移したときのことを思い出すと、こうです。 1. 前は、鍵がブラウザに焼き込まれていた。開いた人に読まれる 2. だから、鍵をサーバー側へ移した 3. すると、鍵は隠れる。代わりに、ブラウザから呼ぶための入り口ができる 4. 今、その入り口には「誰が何回来られるか」が書いていない 隠した分、入り口ができた。引き換えです。そこに条件を書かないと、無制限のままになります。 OWASP の API Security Top 10(2023)は、この「資源の使いすぎ」をこう書いています。使いすぎさせられると、止まる(DoS)だけでなく、CPU やストレージの需要が上がって**運用の費用が増える**形でも表れる(出典 1)。うちのは、落ちるより先にお金のほう。落ちないから、明細が来るまで気付かないこともあります。だから先に決めておく。何回まで、と。 ![鍵をサーバーへ移すと、代わりに窓口ができる。窓口には「誰が・何回」が書かれていない。隠す仕事と使わせすぎない仕事が別であることを示す図](/blog/uploads/api-key-behind-server-not-enough-window.svg) --- ## 窓口に付けるもの 4 つ | 付けるもの | 何を決めるか | 役割 | |---|---|---| | 回数の制限 | 同じ相手が、決めた時間に何回まで叩けるか | 止める | | 使う人の確認 | そもそも誰に使わせるか(合言葉・ログイン) | 分ける | | 上限額と知らせ | いくらまで使うか。超える前に気付けるか | 気付く | | 受ける形と返す形 | 一回の重さと、用途外の使い方を抑える | 抑える | 役割が全部違います。1 つ付ければ済むものではなく、OWASP も複数を並べて挙げています(出典 1)。重さの順でもありません。上から順に見ていきます。 ### 1. 回数の制限:429 で断る 回数の制限は前回入れてあるので、まず呼びすぎは止められます。超えたときに返すのが 429 です。RFC 6585 はこう書いています。429 は、利用者が決められた時間に多く送りすぎたことを示す(出典 3)。何回までかを決めるのは、こっち側です。 面白いのは同じ RFC の続きで、**429 を返す義務は無い**、とあります。攻撃されているときは、返すこと自体が負担になるから。律儀に断らなくてもいい。ただし「返さないのが正しい」という意味ではありません。 ### 2. 使う人の確認:合言葉はアプリに埋めない ログインか、合言葉を 1 つ配るだけでもいい。友達しか使わないなら、足りることもあります。 ここで一つ罠があります。「アプリのほうに合言葉を書いておけば楽では?」。それは前回の鍵と同じになります。ブラウザ用のコードに埋めたら、開いた人に読める。だから埋めない。使う人に入れてもらう。ログインにすれば一人ずつ許可したり止めたりしやすいですが、それでも回数の制限は別に要ります。ログインは利用者を識別する仕組みで、使いすぎを確実に防ぐものではないからです。 窓口のコードで言えば、足すのは順番を守った 1 行です。 ```js // api/typos.js export default async function handler(req, res) { if (tooMany(req)) return res.status(429).end(); if (!allowed(req)) return res.status(401).end(); // ← 足した const r = await fetch(API, { headers: { ... } }); // 鍵を使うのはここ res.json(await r.json()); } ``` 中身は道具に任せていい。**数える処理と確かめる処理を、鍵を使う前に置く**、という順番だけ守ります。後ろに置くと、断る前に呼んでしまう。お金が動くのはその 1 行だからです。 ### 3. 上限額と知らせ:あれば真っ先に、無くても回るように 使っているサービス側の設定で、上限額を決めて、手前で知らせを出す。知らせを入れておけば、明細より先に気付けます。あるなら真っ先に入れていい。 ただし、**この設定が無いサービスもあります**。無かったら、自分で数えて止める。というより、そっちが基本です。前回入れた回数の制限がそれで、上限額の設定は「あれば足す」。無くても回るようにしておきます(出典 1 も、上限額の設定ができないときは請求アラートを、と書いています)。 ### 4. 受ける形と返す形:呼ばれても旨みを残さない これだけ役割が違います。呼ばせないためではなく、呼ばれても用途の外で使う旨みを減らすため。 ```js // api/typos.js(受ける形と、返す形を決める) // 受けるのは { text: 文字列 } だけ。長すぎても断る const t = req.body?.text; if (typeof t !== 'string' || t.length > MAX) return res.status(400).end(); // 返ってきた形もコードで確かめる。合わなければ出さない if (!Array.isArray(found) || !found.every(ok)) return res.status(502).end(); res.json({ found }); ``` うちの窓口は、誤字の位置と候補しか返さない。返ってきた形も、コードで確かめる。裏が AI でも、形が合わない返事は出さないので、文章を書かせるような使い方は**しにくく**なります。絶対ではありません。OWASP の LLM 向けの一覧も、出力の形を決めて決定的なコードで検証することを挙げつつ、完全な防止法があるかは不明、としています(出典 2)。 それに、旨みが無くても、荒らしたいだけの相手は来ます。だから回数の制限も要る。断るのと旨みを減らすのは別々に効いて、両方あって形になります。一回の文字数に上限を置くのも同じ理由で、回数だけでは費用対策として足りません。 ![窓口に付ける 4 つの道具。回数の制限・使う人の確認・上限額と知らせ・受ける形と返す形。それぞれ止める・分ける・気付く・抑えるという別の役割で、鍵を使う 1 行の手前に置くことを示す図](/blog/uploads/api-key-behind-server-not-enough-four.svg) --- ## 「友達にしか教えていないのに、そこまで要る?」 教えた人の数は関係ありません。URL を教えていないだけでは、アクセス制限にはなっていない。公開された窓口は、自動のアクセスに見つかることもあります。人が探すとは限らない。冒頭の「知らないところから」は、たぶんそれです。 OWASP も、この手の悪用には特別な条件は要らず単純なリクエストで成立する、と書いています(出典 1)。だから「小さいから大丈夫」とは言い切れない。教えていないのと、閉めているのは別です。出すなら、鍵をかけてから出す。 --- ## 今日やること 3 つ 1. **サーバー側で、回数と一回の文字数・出力量を決めて止める。** サービス側に利用上限や料金通知があれば足す(無いサービスもある) 2. **誰に使わせるかを決める。** 合言葉はアプリに埋めず、使う人に入れてもらう 3. **受ける形と返す形を決め、返ってきた形もコードで確かめる。** 裏が AI のときほど、ここが効く 鍵を隠したのは無駄ではありません。あれが済んでいるから、今日は窓口だけで済んでいます。隠すのと、使わせすぎないのは別の仕事。両方あって、やっと形になります。 --- ※ ログ・回数・コードは架空の再現で、実測ではありません。金額とサービス名は出していません。「これを付ければ安全」という意味ではなく、4 つは別々の道具で、どこまで付けるかは開けている範囲によります。認証の作り方(合言葉やログインの実装)と CORS はこの記事では扱いません。RFC 6585 と OWASP の原文は 2026 年 9 月時点で確認したものです。 ## 動画版 同じ題材を、YouTube「黒猫開発部」では 2 人がログと窓口のコードを見ながら 6 分で確かめています。記事は 4 つの道具の役割を立ち止まって読む向け、動画は「知らないところから来ている」ところを先に見る向けです。 - 動画は 2026-09-15 公開予定。[チャンネル: 黒猫開発部](https://www.youtube.com/@黒猫開発部) - 前の話: [.env に入れた鍵、ブラウザから丸見えだった(黒猫開発部)](https://youtu.be/bDbrvM23fzc) ## 出典 1. [OWASP API Security Top 10 2023 — API4:2023 Unrestricted Resource Consumption](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) — "Exploitation can lead to DoS due to resource starvation, but it can also lead to operational costs increase such as those related to the infrastructure due to higher CPU demand, increasing cloud storage needs, etc." / "Implement a limit on how often a client can interact with the API within a defined timeframe (rate limiting)." / "Configure spending limits for all service providers/API integrations. When setting spending limits is not possible, billing alerts should be configured instead." / "Define and enforce a maximum size of data on all incoming parameters and payloads, such as maximum length for strings" 2. [OWASP Top 10 for LLM Applications 2025 — LLM01 Prompt Injection](https://genai.owasp.org/llmrisk/llm01-prompt-injection/) — "Specify clear output formats, request detailed reasoning and source citations, and use deterministic code to validate adherence to these formats." / "it is unclear if there are fool-proof methods of prevention for prompt injection." 3. [RFC 6585 — 4. 429 Too Many Requests](https://www.rfc-editor.org/rfc/rfc6585) — "The 429 status code indicates that the user has sent too many requests in a given amount of time ("rate limiting")." / "servers are not required to use the 429 status code" 4. [Stripe — API キー](https://docs.stripe.com/keys) — 公開可能キーはフロントに含めてよい/シークレットキーはバックエンドの外に出さない(前記事で照合済み) 5. [Vite — Env Variables and Modes](https://vite.dev/guide/env-and-mode.html) — "Variables prefixed with `VITE_` will be exposed in client-side source code after Vite bundling."(合言葉をアプリに埋めると読まれる、の根拠)