なぜ必要か
筆者は実務で「chatGPTやgeminiのAPIを使う前提の静的サイト」を扱うことが多い。この時、単純にHTML/JSだけで外部API(OpenAI等)を叩くと、APIキーをブラウザに渡す必要があり、APIキーの内容が漏洩する。また、これは単純にAPIキーを.env等で環境変数に入れても、フロントのJSに埋め込まれるだけで安全にはならない。
なので、APIキーをフロントに渡さない工夫が必要になる。
サンプルコード
基本構成(最小)
- フロント:静的ファイル(HTML/CSS/JS)
- サーバー:PHPで /api/*.php を置く
- 秘密情報:APIキーはサーバ側(.env など)にのみ保持
フロントは外部APIではなく、自分のサーバのPHPにだけリクエストする(例:fetch(“/api/chat.php”))。
仕組み
- ブラウザ → /api/search.php にPOST(検索文など)
- PHPがサーバ内のAPIキーで外部APIへ中継
- PHPが結果だけ返す(キーは絶対に返さない)
サンプル:chatGPT APIを中継して結果を返すだけの最小コード
ディレクトリ例
/public
/api/chat.php
/.env
/vendor
.env
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
/api/chat.php(最小構成)
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
Dotenv\Dotenv::createImmutable(__DIR__ . "/../..")->load();
header("Content-Type: application/json; charset=utf-8");
// フロントからの入力を取得
$body = file_get_contents("php://input");
if ($body === false || $body === '') {
http_response_code(400);
echo json_encode(["error" => "empty request body"]);
exit;
}
// APIキー取得
$apiKey = $_ENV['OPENAI_API_KEY'] ?? '';
if ($apiKey === '') {
http_response_code(500);
echo json_encode(["error" => "API key not set"]);
exit;
}
// chatGPT API にそのまま中継
$ch = curl_init("https://api.openai.com/v1/chat/completions");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer {$apiKey}",
],
CURLOPT_POSTFIELDS => $body,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
if ($response === false) {
http_response_code(502);
echo json_encode(["error" => curl_error($ch)]);
curl_close($ch);
exit;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// chatGPTのレスポンスをそのまま返す
http_response_code($httpCode);
echo $response;
フロント側(例)
// 送信の作成
const payload = {
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "必ずJSONのみ返してください。" },
{ role: "user", content: "こんにちは。" }
],
temperature: 0
};
// 送信と受信
const res = await fetch("/api/chat.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
// 受信物
const data = await res.json();
console.log(data);
このサンプルの位置づけ(重要)
これはあくまで「APIキーをブラウザに出さない」ことだけを達成する最小例なので、そのままでは実務レベルでは使えないので注意。実務では以下を必ず追加検討する。
最低限追加すべきもの
-
入力サイズ制限(prompt爆撃対策)
-
レート制限(課金事故防止)
-
エラーメッセージの簡略化(内部情報を返さない)
-
MOCK_MODE(ローカル検証用)
あえてやっていないこと
-
バリデーションの厳格化
-
認証
-
ログ保存


コメント