- 概要(APIキーは「本人確認の鍵」。扱いは最小露出・安全保管・適切送信)
- リスクの理解(直書き・共有・ログ漏れが最大の落とし穴)
- 保管の型(環境変数・dotenv・シークレット管理)
- 送信の型(HTTPヘッダーで最小情報を安全に渡す)
- 記録の型(ログ・エラー・監査で漏らさない)
- ライフサイクル管理(発行・保管・利用・ローテーション)
- リポジトリとCIの衛生(コミット防止・検知・復旧)
- フロントエンドでは使わない(キーはサーバーだけ)
- テスト戦略(偽キー・モック・安全な検証)
- 例題(安全な設定と利用の最小構成)
- よくあるつまずきと対策(URLに付与・マルチテナント・キー使い回し)
- まとめ(「保管は見えない場所」「送信は最小」「記録は伏字」)
概要(APIキーは「本人確認の鍵」。扱いは最小露出・安全保管・適切送信)
APIキーはサービスを利用するための秘密の識別子です。コードに直書きせず、環境変数やシークレット管理で安全に保管し、HTTPヘッダーなど適切な方法で送信します。重要なのは「保管(見えない場所)」「送信(必要最低限)」「記録(漏らさない)」の3本柱と、事故が起きた時にすぐ交換できる運用(ローテーション)です。
リスクの理解(直書き・共有・ログ漏れが最大の落とし穴)
直書きの危険
コードにキーを直書きすると、Gitにコミットされ社内外へ拡散します。公開リポジトリはもちろん、社内でも履歴に残り続け、退職後も閲覧され得ます。秘密は「コードの外」に置くのが鉄則です。
共有の危険
チャットやメールでキーを送ると、誤送信や転職時の持ち出しのリスクが増します。権限管理されたシークレットストア(クラウドやCIのSecrets)を使い、閲覧権限を最小化します。
ログ漏れの危険
例外やデバッグ出力にキーが含まれると、ログ収集システム経由で第三者が閲覧可能になります。ログは「キーそのもの」を記録しない設計にし、必要なら伏字化(マスク)します。
保管の型(環境変数・dotenv・シークレット管理)
開発環境:dotenvで読み込み、環境変数から取得
- 手順: プロジェクト直下の .env にキーを記載 → 起動時に読み込み → os.getenv で取り出す
- ポイント: .env は .gitignore に必ず追加し、共有用にダミーの .env.example を配布します
# .env(コミットしない)
API_KEY=sk_example_123
BASE_URL=https://api.example.com
# 設定読み込み(python-dotenvを利用)
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("API_KEY") # 必須は起動時に検証
if not API_KEY:
raise RuntimeError("API_KEYが未設定です")
Python本番環境:プラットフォームのシークレット機能で注入
- 手順: クラウドやCI/CDのSecretsに保管 → デプロイ時に環境変数として注入
- ポイント: 本番に .env ファイルは持ち込まない。権限は「必要なサービスだけ」に絞る(最小権限)
送信の型(HTTPヘッダーで最小情報を安全に渡す)
ヘッダーで送る(Bearerやカスタムヘッダー)
多くのAPIは Authorization: Bearer TOKEN または X-API-Key: TOKEN の形でキーを受け取ります。URLクエリにキーを載せるのは避け、HTTPSで送信します。
import requests, os
BASE_URL = os.getenv("BASE_URL", "https://api.example.com")
API_KEY = os.getenv("API_KEY", "")
def get_user(uid: str, timeout: float = 5.0) -> dict:
if not API_KEY:
raise RuntimeError("APIキーがありません")
r = requests.get(
f"{BASE_URL}/users/{uid}",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=timeout,
)
r.raise_for_status()
return r.json()
Python最小送信の原則
- 必要なリクエストだけに付与: 全リクエストへ無差別付与を避ける
- スコープを絞る: 可能なら「読み取り専用」など最小権限のキーを使う
記録の型(ログ・エラー・監査で漏らさない)
キーはログに出さない(伏字化・要約のみ)
- やること: 先頭・末尾だけを出す、長さやハッシュで識別
- 避ける: キー全文の表示や平文保存
import logging, os
def mask(token: str) -> str:
return token[:4] + "..." + token[-4:] if token and len(token) > 8 else "***"
API_KEY = os.getenv("API_KEY")
logging.info("API_KEY: %s", mask(API_KEY))
Python例外の扱い
通信エラーや認証エラーは、URLやステータスだけ記録し、キーは記録しない。必要なら「有効期限切れの可能性」など推測を短く残す。
ライフサイクル管理(発行・保管・利用・ローテーション)
発行と保管
- 発行: 管理コンソールでキーを作成し、すぐ安全なストアへ保管
- 保管: パスワードマネージャやSecretsに入れ、個人の端末に平文で置かない
利用と期限
- 期限設定: 可能なら有効期限付きキーを活用し、期限前に更新手順を用意
- 失効: 事故(コミット等)発生時は即失効→新キー発行→デプロイ
ローテーションの実装
- 二重運用: 新旧キーを同時に使える期間を設け、切替が成功したら旧キーを失効
- 機能フラグ: 環境変数や設定でキー切替を即時に可能にしておく
リポジトリとCIの衛生(コミット防止・検知・復旧)
.gitignore とダミーファイル
- .gitignore: .env、*.pem、secrets/ などを必ず除外
- .env.example: キー名だけ残し、値はダミーで配布
# secrets
.env
*.pem
secrets/
誤コミットの検知と対応
- 検知: CIで秘密文字列パターン(sk- など)をスキャン
- 対応: 履歴から完全削除(filter-repoなど)→キー失効→周知・ローテーション
フロントエンドでは使わない(キーはサーバーだけ)
原則
ブラウザやモバイルアプリにAPIキーを埋め込むと、ユーザーに見えてしまいます。フロントは自分のバックエンドに問い合わせ、バックエンドが外部APIへキー付きでアクセスします。
代替策
- バックエンド中継: フロント→自社API→外部API
- 認可: フロントは自社の認可(セッションやJWT)でアクセス制御
テスト戦略(偽キー・モック・安全な検証)
偽キーを使う
テスト時はダミー値(TEST_KEY)で通すか、外部APIをモックしてキー検証を回避します。実環境に近い統合テストは「テスト用スコープのキー」で、権限を最小化。
from unittest import mock
@mock.patch.dict(os.environ, {"API_KEY": "TEST_KEY"})
def test_build_headers():
# ヘッダー構築の単体テスト
...
Python例題(安全な設定と利用の最小構成)
例1:dotenvで安全に読み込み、requestsで送信
from dotenv import load_dotenv
import os, requests
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL", "https://api.example.com")
def require(v: str | None, name: str) -> str:
if not v:
raise RuntimeError(f"{name}が未設定です")
return v
API_KEY = require(API_KEY, "API_KEY")
def fetch(uid: str) -> dict:
r = requests.get(f"{BASE_URL}/users/{uid}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=5)
r.raise_for_status()
return r.json()
Python例2:FastAPIでキーを環境から注入し、ログはマスク
import os
from fastapi import FastAPI, HTTPException
import logging, requests
app = FastAPI()
API_KEY = os.getenv("API_KEY") or ""
logging.info("API_KEY: %s", API_KEY[:4] + "...")
@app.get("/users/{uid}")
def get_user(uid: str):
if not API_KEY:
raise HTTPException(status_code=500, detail="APIキー未設定")
try:
r = requests.get("https://httpbin.org/get", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=5)
r.raise_for_status()
return r.json()
except requests.RequestException as e:
raise HTTPException(status_code=502, detail="外部API失敗")
Pythonよくあるつまずきと対策(URLに付与・マルチテナント・キー使い回し)
URLクエリにキーを載せる
ブックマークやリファラに残り漏えいします。ヘッダーへ移し、HTTPSを徹底します。
マルチテナントでキーの取り違え
テナントIDとキーを紐付け、ミドルウェアで「テナント→鍵選択」を一箇所に集約。個別の関数で環境変数を都度読むより安全です。
ひとつのキーを使い回す
用途や権限ごとにキーを分ける(読み取り用、書き込み用、テスト用)。事故時の影響範囲を小さくできます。
まとめ(「保管は見えない場所」「送信は最小」「記録は伏字」)
APIキーは本人確認の鍵。直書きせず環境変数やシークレット管理に保管し、HTTPS+ヘッダーで必要最小限だけ送る。ログや例外で漏らさず、事故時は素早くローテーション。開発はdotenv、本番はプラットフォームのSecretsに寄せる。フロントへは持ち込まずバックエンドで扱い、テストはダミーやモックで安全に行う。この型を守れば、初心者でも短い手順で「安全・柔軟・再現性の高い」APIキー運用が実現できます。
