Python | Web / API:API キーの扱い

Python Python
スポンサーリンク
  1. 概要(APIキーは「本人確認の鍵」。扱いは最小露出・安全保管・適切送信)
  2. リスクの理解(直書き・共有・ログ漏れが最大の落とし穴)
    1. 直書きの危険
    2. 共有の危険
    3. ログ漏れの危険
  3. 保管の型(環境変数・dotenv・シークレット管理)
    1. 開発環境:dotenvで読み込み、環境変数から取得
    2. 本番環境:プラットフォームのシークレット機能で注入
  4. 送信の型(HTTPヘッダーで最小情報を安全に渡す)
    1. ヘッダーで送る(Bearerやカスタムヘッダー)
    2. 最小送信の原則
  5. 記録の型(ログ・エラー・監査で漏らさない)
    1. キーはログに出さない(伏字化・要約のみ)
    2. 例外の扱い
  6. ライフサイクル管理(発行・保管・利用・ローテーション)
    1. 発行と保管
    2. 利用と期限
    3. ローテーションの実装
  7. リポジトリとCIの衛生(コミット防止・検知・復旧)
    1. .gitignore とダミーファイル
    2. 誤コミットの検知と対応
  8. フロントエンドでは使わない(キーはサーバーだけ)
    1. 原則
    2. 代替策
  9. テスト戦略(偽キー・モック・安全な検証)
    1. 偽キーを使う
  10. 例題(安全な設定と利用の最小構成)
    1. 例1:dotenvで安全に読み込み、requestsで送信
    2. 例2:FastAPIでキーを環境から注入し、ログはマスク
  11. よくあるつまずきと対策(URLに付与・マルチテナント・キー使い回し)
    1. URLクエリにキーを載せる
    2. マルチテナントでキーの取り違え
    3. ひとつのキーを使い回す
  12. まとめ(「保管は見えない場所」「送信は最小」「記録は伏字」)

概要(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キー運用が実現できます。

タイトルとURLをコピーしました