背景
React Native (Expo) アプリで OpenAI API を使って英語フラッシュカードを自動生成するアプリを開発しています。
当初は OpenAI API に直接リクエストを送っていましたが、現在は以下のような構成に移行しました。
現在は「Expo→API Gateway→Lambda→OpenAI」という構成に移行しました。
メリット
項目 | 説明 |
---|---|
セキュリティ向上 | APIキーをクライアント側に埋め込まない |
不正利用防止 | コード分析やネットワーク監視でもAPIキーが見られない |
利用量管理 | 誰がどれだけ使ったかを追跡可 |
ロジック加工 | 入力値検証やログ出力も可能 |
当初の構成
request.js
// アプリ側
const API_KEY = *************************
const response = await axios.post('https://api.openai.com/v1/chat/completions', {
model: 'gpt-4o-mini',
messages: [...],
}, {
headers: {
Authorization: `Bearer ${API_KEY}`
}
})
問題点
- アプリにAPIキーが埋め込まれる
- ビルド時にAPIキーがコード内に埋め込まれ、添付JSから取得可能
- 不正利用されると請求額が増える恐れ
改善後
Expoアプリから lambdaへのリクエスト
request.js
const API_URL = ****************
const API_KEY = ****************
const response = await axios.post(API_URL, { prompt }, {
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
})
lambdaのコード
index.mjs
export const handler = async (event) => {
const body = JSON.parse(event.body || '{}')
const prompt = body.prompt || 'default'
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [...],
})
})
const data = await response.json()
return {
statusCode: 200,
body: *************** })
}
}
まとめ
ExpoでOpenAI APIを利用する際は、「API Gateway + Lambda」でキーを隠蔽しながら、ロジックを分離して実装しました。
おまけ
おまけ:なぜ .env や eas.json で隠しても完全ではない?
Expoのビルドプロセスで .env や eas.json にキーを定義しても、最終的にはバンドルされたJSコードに「値」が入るため、秘匿にはなりません。
重要なAPIキーは必ずサーバーサイド(Lambdaなど)で保持しましょう。