API設計と認証・認可
目次
- 概要
- API設計の基本
- HTTPの意味を設計に使う
- エラー設計
- 契約としてのAPI
- OpenAPIで何を記述するか
- バージョニング
- 認証と認可
- 認可モデル
- レート制限と不正利用対策
- 設計上の注意
- APIの観測性
- REST APIセキュリティの設計レビュー
- REST API 設計の実践的ガイド
- 認証方式の比較と実装
- API セキュリティの実装
- API バージョニング戦略
- gRPC の活用
- W3C 標準との統合
- キャッシング戦略
- REST API 成熟度モデル(Richardson Maturity Model)
- API セキュリティ NIST ガイドライン
- API Gateway パターン
- GraphQL セキュリティ考慮
- API テスト戦略
- OAuth 2.0 の詳細フロー と RFC 9700 セキュリティアップデート
- NIST SP 800-63B-3(Digital Identity Guidelines)の認証要件
- OWASP API Security Top 10(2023)
- gRPC のセキュリティ設計
- まとめ
- 参考文献
概要
接続点は壊れにくく、誤用されにくく設計する
APIは単にデータを返す口ではなく、システム境界そのものです。使いやすさ、互換性、認証、認可、エラー表現、観測性まで含めて設計します。
良いAPIは「呼べる」だけでなく、「誤用しにくい」「壊しにくい」「権限の境界が明確」という性質を持ちます。
この章で重視すること
API設計の基本
まず決めるべきなのは、何を資源として扱い、どこまでを操作単位にするかです。REST、GraphQL、gRPCは表現や通信の形が違うだけで、境界の設計そのものが不要になるわけではありません。
押さえるべき論点は次です。
- URLやメソッドの意味
- IDの安定性
- ページング
- フィルタと並び替え
- 冪等性
- 部分更新
- エラー形式
HTTP APIでは、独自の雰囲気だけで設計せず、HTTPメソッド、ステータスコード、キャッシュ、条件付きリクエスト、認証ヘッダーの意味を標準に寄せます。とくに公開APIでは、実装の都合よりも「利用者が予測できること」を優先します。
OpenAPIのような機械可読な仕様を用意すると、ドキュメント、SDK生成、モック、契約テスト、レビューを同じ定義から始められます。API仕様は納品物ではなく、変更時に一緒に更新される設計資産として扱います。
REST
RESTでは、resourceをURLとして表し、HTTP methodで操作を表します。重要なのは、URLの見た目よりもmethodの意味、status code、cache、冪等性を一貫して扱うことです。
例:
GET /users/123
PATCH /users/123
DELETE /users/123
POST /orders
GET は副作用を持たせない、PUT は置き換え、PATCH は部分更新、POST は新規作成や処理開始、というように意味をそろえます。
GraphQL
GraphQLは、クライアントが必要な形を問い合わせやすい点が強みです。複数画面やフロントエンドの要求が多様な場合に便利です。一方で、権限、N+1、query cost、schema evolutionをきちんと設計しないと運用が難しくなります。
gRPC
gRPCはschema-firstで、Protocol Buffersを使って型付きのRPCを定義します。内部サービス間通信や高頻度通信に向きます。HTTP/JSONより人間が直接読みやすいわけではないため、公開APIと内部APIで使い分けることがあります。
HTTPの意味を設計に使う
HTTP APIを設計するときは、HTTPを単なる通信路ではなく、意味を持ったアプリケーションプロトコルとして使います。RFC 9110は、method、status code、header、cache、content negotiationなどの意味を定義しています。独自ルールで上書きしすぎると、クライアント、proxy、cache、監視、SDKがHTTPの前提を使えなくなります。
methodの性質
| method | 主な用途 | 安全性 | 冪等性 | 設計上の注意 |
|---|---|---|---|---|
GET |
取得 | safe | idempotent | 副作用を持たせない |
POST |
作成、処理開始 | unsafe | 原則非冪等 | idempotency keyを検討する |
PUT |
resource全体の置き換え | unsafe | idempotent | 部分更新と混同しない |
PATCH |
部分更新 | unsafe | 実装次第 | patch形式と競合制御を決める |
DELETE |
削除 | unsafe | idempotentに設計しやすい | 物理削除か論理削除かを決める |
ここでいうsafeは「読み取りであり、利用者が要求した状態変化を起こさない」という意味です。ログ記録やメトリクス更新のような内部的な副作用はありますが、注文作成や残高変更のような業務上の副作用をGETに入れてはいけません。
status codeを情報設計として使う
RFC 9110では、クライアントは未知のstatus codeでも先頭桁のclassを理解する必要があります。つまり、2xx、3xx、4xx、5xxの分類はAPI利用者にとって重要なシグナルです。
2xx:
要求は受け入れられた、または成功した
4xx:
呼び出し側が修正すべき問題
5xx:
サーバ側、依存先、または一時的な障害
アプリケーション固有の詳細はbodyのcodeで表し、HTTP status codeは大分類として使うと、監視やretry policyが組みやすくなります。
エラー設計
APIのエラーは、開発者体験と運用性に直結します。
含めたい情報は次です。
- machine-readableなerror code
- 人間が読めるmessage
- request id / trace id
- retry可能か
- validation errorのフィールド
ただし、内部実装や機密情報を返してはいけません。
認証失敗、認可失敗、入力エラー、競合、rate limit、内部エラーは、利用者が次に取るべき行動が違います。すべてを400や500に寄せると、クライアントは再試行すべきか、入力を直すべきか、権限を確認すべきか判断できません。
| 状況 | 代表的な扱い | 利用者が取る行動 |
|---|---|---|
| 入力が不正 | 400 / validation error |
入力を修正する |
| 認証されていない | 401 |
ログインまたはtoken更新 |
| 権限がない | 403 |
権限付与やresource確認 |
| 対象がない | 404 |
IDや公開範囲を確認 |
| 競合している | 409 |
最新状態を取得して再実行 |
| rate limit | 429 |
待って再試行 |
| サーバ内部 | 5xx |
retry policyに従う |
契約としてのAPI
APIは実装の入口ではなく、利用者との契約です。契約には、URL、method、schema、認証方式、エラー形式、rate limit、廃止方針が含まれます。
契約を中心に置くと、フロントエンド、バックエンド、外部利用者、テストが同じ前提を共有できます。仕様と実装がずれると、APIは静かに壊れます。
互換性の基本
公開済みAPIで安全に行いやすい変更:
- optional fieldを追加する
- enumを追加する。ただしunknown値に耐えられる設計にする
- 新しいendpointを追加する
- エラーに補助情報を追加する
危険な変更:
- 既存fieldを削除する
- 型を変える
- 必須fieldを増やす
- status codeの意味を変える
- 認可条件を暗黙に変える
API設計では、最初の実装よりも、2回目以降の変更で壊れないことが重要です。
OpenAPIで何を記述するか
OpenAPI Specificationは、HTTP APIを人間と機械が理解できる形で記述する仕様です。仕様には、paths、operations、parameters、request body、responses、schemas、security schemesなどを含めます。
openapi: 3.1.0
info:
title: Orders API
version: 1.0.0
paths:
/orders/{orderId}:
get:
operationId: getOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
responses:
"200":
description: Order found
"404":
description: Order not found
OpenAPIのresponsesは、成功だけでなく、分かっているエラーも書きます。仕様では、少なくとも1つのresponse codeが必要で、既知のエラーを個別に定義し、未知のエラーにはdefaultを使えます。これにより、クライアントは失敗時の分岐を事前に実装できます。
セキュリティ方式
OpenAPIでは、API key、HTTP authentication、mutual TLS、OAuth2、OpenID Connectなどをsecurity schemeとして表せます。ここで重要なのは「認証方式をドキュメントに書く」だけではなく、operationごとにどのschemeとscopeが必要かを明示することです。
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
paths:
/me:
get:
security:
- bearerAuth: []
仕様に認証方式が書かれていないAPIは、SDK生成やテストで安全側に倒しにくくなります。
バージョニング
APIは一度使われると、簡単には変えられません。破壊的変更を避けるため、互換性の方針を決めます。
- optional fieldを追加する
- unknown fieldを無視できるようにする
- enumの追加に備える
- 古いversionの終了時期を明示する
- consumer-driven contractを使う
バージョニングは「URLにv1を入れるか」だけではありません。破壊的変更をどう定義し、どの期間サポートし、どのように移行を知らせるかまで含めます。
Deprecation policy:
- 新APIを先に公開する
- 旧APIにdeprecation headerや告知を出す
- 利用状況を観測する
- 移行期限を明示する
- 期限後に段階的に停止する
認証と認可
認証は「誰か」を確認すること、認可は「何ができるか」を決めることです。ここを混同すると、権限設計が壊れます。
実務では、次の分離が重要です。
- 認証 password、passkey、federation、token発行
- セッション / token管理 有効期限、失効、更新
- 認可 role、attribute、resource単位の判断
OAuthとOpenID Connect
OAuthはdelegated authorizationのための枠組みで、OpenID Connectはその上でidentity情報を扱う層です。ログインを作るときにOAuthとOIDCを混同すると、access tokenとID tokenの扱いを誤りやすくなります。
- access token APIへのアクセス権を表す
- ID token 認証結果とユーザー情報を表す
- refresh token 新しいaccess tokenを得るために使う
Webアプリケーションではauthorization code flow + PKCEを基本に考えます。
認可モデル
- RBAC roleに権限を集める
- ABAC 属性にもとづいて判断する
- ReBAC 関係性にもとづいて判断する
単純なシステムではRBACで十分なことが多いですが、組織、所有関係、共有、委任が複雑になると、resource単位の認可が必要になります。
BOLAを防ぐ
OWASP API Security Top 10のAPI1:2023はBroken Object Level Authorizationです。これは、API利用者がURL、query、bodyなどに含まれるobject IDを操作し、他人のresourceへアクセスできてしまう問題です。
GET /shops/{shopId}/revenue
危険:
ログイン済みならshopIdをそのまま信じる
必要:
現在の利用者が、そのshopIdに対してread権限を持つかを確認する
BOLA対策では、IDを推測しにくくするだけでは足りません。UUIDを使っても、漏れたIDや共有URL経由で権限チェックを迂回できる場合があります。すべてのresourceアクセスで、主体、操作、対象resourceの組み合わせを確認します。
オブジェクト属性レベルの認可
OWASP API3:2023は、objectのfield単位の認可不備です。たとえば管理者だけが見られるcostPriceやinternalNoteを、一般ユーザー向けAPIが返してしまうケースです。
{
"id": "order_123",
"total": 5000,
"internalRiskScore": 92
}
API設計では、DB modelをそのままresponseにしないことが重要です。利用者、権限、画面用途に応じてDTOやview modelを分けます。
レート制限と不正利用対策
公開APIでは、正しい認証だけでは足りません。大量リクエスト、bot、credential stuffing、スクレイピング、誤実装から守る必要があります。
見るべき観点は次です。
- client / user / tokenごとのrate limit
- burstとsustained trafficの区別
- idempotency key
- replay protection
- request signing
- audit log
OWASP API2:2023はBroken Authenticationです。認証endpoint、password reset、token refresh、メールアドレス変更のようなsensitive operationは攻撃対象になりやすいので、通常APIより厳しく扱います。
通常API:
userごとのrate limit
tokenごとのrate limit
認証API:
IP / account / device / credential pairごとの制限
credential stuffing検知
MFAや再認証
password reset abuse対策
GraphQLやbatch APIでは、1リクエスト内に複数操作を詰められるため、HTTPリクエスト数だけでrate limitするとすり抜けが起きます。operation数、query complexity、対象resource数も制限します。
設計上の注意
- tokenに権限を持たせすぎない
- API gatewayだけで完結したつもりにならない
- バックエンドでもresource単位で認可確認する
- エラーから過剰な情報を漏らさない
APIインベントリ
OWASP API9:2023はImproper Inventory Managementです。どのAPIが存在し、誰が使い、どのversionが生きていて、どのdataを扱うかが分からない状態は、それ自体がリスクです。
API inventoryには次を含めます。
- endpointとowner
- versionとdeprecation status
- 認証方式
- 必要なscope/role
- 扱うdata分類
- external/internalの区分
- last accessと主要consumer
- OpenAPI specへのリンク
古いAPI、実験用API、管理用APIは、ドキュメントから漏れた瞬間に守られにくくなります。
APIの観測性
APIは外部との契約なので、失敗したときに追える必要があります。
- request id
- structured log
- latency histogram
- status codeごとの集計
- consumerごとのerror rate
- schema validation error
認証・認可の失敗も、ユーザー体験とセキュリティ調査の両方から観測できるようにします。
- OWASP API1:2023 Broken Object Level Authorization
- OWASP API2:2023 Broken Authentication
- OWASP API3:2023 Broken Object Property Level Authorization
REST APIセキュリティの設計レビュー
OWASP REST Security Cheat Sheetでは、REST APIの安全性をHTTPS、access control、JWT、API key、HTTP method制限、content type、management endpoint、error handling、audit log、security headers、CORSなど複数の観点で整理しています。API設計では、これらを実装後のチェック項目ではなく、設計レビューの入力にします。
レビュー観点
| 観点 | 確認すること |
|---|---|
| HTTPS | credential、token、API keyが平文で流れない |
| access control | endpointごとに認可を行い、object IDを信用しない |
| JWT | 署名、issuer、audience、有効期限、失効方針を確認する |
| API key | 利用者識別やquotaには使えても、単独の強い認証として過信しない |
| HTTP method | 不要なmethodを許可しない |
| content type | request/responseのcontent typeを検証する |
| error handling | 内部実装やstack traceを返さない |
| audit log | 認証、認可、管理操作、重要resource操作を追える |
| CORS | 許可origin、credential送信、preflightを明示する |
特に管理用endpointは、通常のユーザー向けAPIより強い認可、ネットワーク制限、監査ログが必要です。/admin、/internal、/debug、/metrics のようなendpointが公開経路に混ざると事故につながります。
REST API 設計の実践的ガイド
OpenAPI 仕様(spec.openapis.org)は、REST API の標準化書式です。
OpenAPI 3.0 の基本構造
openapi: 3.0.0
info:
title: API Title
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: User found
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
'404':
description: User not found
OpenAPI を定義することで、クライアントコード生成、ドキュメント自動生成、モック API 自動生成が実現できます。
GraphQL vs REST
| 属性 | REST | GraphQL |
|---|---|---|
| クエリ方式 | 固定エンドポイント | 柔軟なクエリ言語 |
| オーバーフェッチ | あり(不要なフィールド含む) | なし(必要なフィールドのみ) |
| アンダーフェッチ | あり(複数リクエスト必要) | なし(1リクエストで多くの関連データ) |
| キャッシング | HTTP キャッシュ活用可能 | クエリごと異なるため複雑 |
| デバッグ | 比較的容易 | クエリの複雑性が増す可能性 |
| ラーニングカーブ | 低 | 高(GraphQL スキーマ習得必要) |
spec.graphql.org で GraphQL 仕様が定められており、query, mutation, subscription が基本操作です。
認証方式の比較と実装
OAuth 2.0 フロー
OpenID.net では、OAuth 2.0 の複数フロー(Authorization Code, Implicit, Resource Owner, Client Credentials)が定義されています。
Authorization Code フロー(最も安全):
1. ユーザーがログインボタンをクリック
↓
2. Authorization Server にリダイレクト
↓
3. ユーザーが許可
↓
4. Authorization Code を返却
↓
5. Backend が Code と Secret で Token Exchange
↓
6. Access Token 取得
↓
7. Access Token で Resource Access
JWT (JSON Web Token) の実装
RFC 7519 (JWT) では、Header.Payload.Signature の形式が定義されています。
import jwt
import json
from datetime import datetime, timedelta
# JWT の作成
payload = {
'user_id': 123,
'username': 'alice',
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow()
}
secret = 'your-secret-key'
token = jwt.encode(payload, secret, algorithm='HS256')
# JWT の検証
try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(decoded)
except jwt.ExpiredSignatureError:
print("Token expired")
except jwt.InvalidTokenError:
print("Invalid token")
NIST 認証ガイドライン
csrc.nist.gov の NIST SP 800-63B では、認証強度の分類が定義されています。
| レベル | 要件 | 例 |
|---|---|---|
| AAL1(Assurance Level 1) | 単一要素認証 | パスワード のみ |
| AAL2 | 多要素認証 | パスワード + OTP |
| AAL3 | 多要素認証(推奨) | パスワード + 生体認証 or ハードウェアトークン |
モダン実装では、少なくとも AAL2(多要素認証)が推奨されます。
API セキュリティの実装
OWASP API Security Top 10 では、API 固有のセキュリティリスクが列挙されています。
主要なセキュリティ対策
# 1. Rate Limiting(DDoS 対策)
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/api/users')
@limiter.limit("100/hour")
def get_users():
return {...}
# 2. CORS の適切な設定
from flask_cors import CORS
CORS(app, resources={
r"/api/*": {
"origins": ["https://trusted-domain.com"],
"methods": ["GET", "POST"]
}
})
# 3. Input Validation
from pydantic import BaseModel, validator
class UserInput(BaseModel):
name: str
email: str
@validator('email')
def email_must_be_valid(cls, v):
if '@' not in v:
raise ValueError('Invalid email')
return v
# 4. SQL Injection 対策(ORM 使用)
user = User.query.filter_by(email=user_input.email).first()
# Raw SQL は避ける
# 5. Authentication Token の保護
headers = {
'Authorization': f'Bearer {access_token}'
}
# HTTPS 強制(HTTP では送信しない)
API バージョニング戦略
API の後方互換性を保つため、バージョニングが必要です。
バージョニング方式
1. URL パス
/api/v1/users
/api/v2/users
2. クエリパラメータ
/api/users?version=2
3. ヘッダ
Accept: application/vnd.myapi.v2+json
4. コンテンツネゴシエーション
Accept: application/json; version=2
ほとんどのプロジェクトでは、URL パスベースのバージョニングが採用されています。
サンセット戦略
v1: 廃止予定(1年以内)
v2: アクティブ
v3: 最新
段階的な廃止:
1. 廃止通知(6ヶ月前)
2. 非推奨ヘッダ追加(Deprecation: true)
3. 段階的なサンセット
4. 最終的に削除
gRPC の活用
Google が開発した gRPC は、Protocol Buffers を使った高性能 RPC フレームワークです。
REST vs gRPC
| 属性 | REST | gRPC |
|---|---|---|
| プロトコル | HTTP/1.1 | HTTP/2 |
| 形式 | JSON | Protocol Buffers(バイナリ) |
| 性能 | 中 | 高(バイナリで高速) |
| ブラウザ対応 | 良好 | 要 gRPC-Web |
| デバッグ | 容易(JSON テキスト) | 複雑(バイナリ) |
| 学習コスト | 低 | 中(IDL 定義習得) |
grpc.io では、Protocol Buffers の仕様と複数言語の実装が提供されています。
gRPC の基本実装
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (Empty) returns (stream User);
}
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message Empty {}
W3C 標準との統合
W3.org の CORS(Cross-Origin Resource Sharing)仕様では、クロスオリジンリクエストの安全な処理が定義されています。
CORS ヘッダ
リクエスト(ブラウザから):
Origin: https://example.com
レスポンス(サーバーから):
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
CORS 設定を厳しくしすぎると API の利用者が増えず、緩すぎるとセキュリティリスクが高まります。
キャッシング戦略
API レスポンスのキャッシングは、パフォーマンス向上に重要です。
HTTP キャッシュヘッダ
Cache-Control: public, max-age=3600
↓ 3600秒(1時間)、パブリックキャッシュ有効
Cache-Control: private, max-age=300
↓ 300秒(5分)、プライベート(ブラウザのみ)
Cache-Control: no-cache, must-revalidate
↓ キャッシュするが常に検証
ETag を使った条件付きリクエストで、キャッシュの効率性を向上させます。
from flask import make_response
from hashlib import md5
@app.route('/api/users')
def get_users():
data = {...}
etag = md5(str(data).encode()).hexdigest()
response = make_response(data)
response.headers['ETag'] = etag
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
REST API 成熟度モデル(Richardson Maturity Model)
API の設計成熟度を4段階で評価します:
レベル0: HTTP のみ
POST /api
{
"method": "get_users",
"id": 1
}
→ HTTP は単なる通信路、意味を持たない
レベル1: リソース指向
GET /users/1
→ リソースを個別化するが、HTTP method は使わない
レベル2: HTTP Verb の活用
GET /users リソース一覧
POST /users リソース作成
GET /users/1 リソース詳細
PUT /users/1 リソース更新
DELETE /users/1 リソース削除
→ HTTP method で操作の意図を表現
レベル3: HATEOAS (Hypermedia As The Engine Of Application State)
{
"id": 1,
"name": "Alice",
"_links": {
"self": { "href": "/users/1" },
"all": { "href": "/users" },
"edit": { "href": "/users/1", "method": "PUT" },
"delete": { "href": "/users/1", "method": "DELETE" }
}
}
→ クライアントが次のアクション候補をサーバーから得る
HATEOAS の利点は、API の URL が変更になっても、クライアント側の修正が最小限で済むことです。
API セキュリティ NIST ガイドライン
NIST SP 800-63-4 では、デジタル認証のベストプラクティスが定義されています。
パスワード管理 (SP 800-63B-4)
推奨:
- 最小長: 8文字(ユーザー選択の場合)
- 辞書チェック: 既知の侵害パスワード DB との照合
- 定期的な変更: 不要(侵害時のみ)
非推奨:
- 複雑性要件の強要(大文字、記号など)
- パスワード定期変更の強制
- セキュリティ質問による検証
多要素認証 (MFA)
レベル別実装:
Lv1: 単一要素(パスワード)
- リスク: 侵害時に全権限喪失
Lv2: 2要素認証 (2FA)
- 何かを知っている: パスワード
- 何かを持っている: TOTP, SMS, ハードウェアキー
- 相応のセキュリティ向上
Lv3: 複数要素 + 追加検証
- 生体認証, エンタープライズ SSO 等
実装例(Python + PyOTP):
import pyotp
import qrcode
from io import BytesIO
# ユーザーが初回セットアップ時に TOTP を生成
def setup_mfa(user_id):
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret)
# QR コード生成
qr = qrcode.QRCode()
qr.add_data(totp.provisioning_uri(name=user_id, issuer_name='MyApp'))
img = qr.make_image()
# User に QR を表示、secret をバックアップ用に保存
return secret, img
# ログイン時に TOTP を検証
def verify_mfa(user_id, token):
secret = db.get_user_secret(user_id)
totp = pyotp.TOTP(secret)
return totp.verify(token)
API Gateway パターン
複数のマイクロサービスを統一インターフェースで公開:
クライアント
↓
API Gateway
├─ 認証・認可
├─ Rate limiting
├─ キャッシング
├─ ロギング
└─ ルーティング
↓
┌───┬─────┬────────┐
User Service
Document Service
Payment Service
実装例(FastAPI + proxy):
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer
app = FastAPI()
security = HTTPBearer()
@app.middleware("http")
async def add_process_time_header(request, call_next):
# 認証
if not request.headers.get("Authorization"):
raise HTTPException(status_code=401)
# Rate limiting チェック
user_id = extract_user_id(request)
if is_rate_limited(user_id):
raise HTTPException(status_code=429)
response = await call_next(request)
return response
@app.get("/users/{user_id}")
async def get_user(user_id: int, token: str = Depends(security)):
# User Service にプロキシ
response = httpx.get(f"http://user-service/users/{user_id}")
return response.json()
GraphQL セキュリティ考慮
GraphQL は柔軟性が高い反面、セキュリティリスクが増えやすいです。
クエリ深度の制限
# ネストが深すぎるクエリを防止
MAX_DEPTH = 5
def validate_depth(document):
def count_depth(node, depth=0):
if depth > MAX_DEPTH:
raise ValueError(f"Query depth exceeds {MAX_DEPTH}")
for field in node.get_fields():
count_depth(field, depth + 1)
count_depth(document)
複雑度に基づくレート制限
# 単純な行数制限ではなく、query の複雑度を計算
def estimate_query_cost(query):
"""
各フィールドのコストを集計
users { id } = 1
users { posts {} } = 1 + 10*1 = 11
"""
cost = 0
for field in query.fields:
cost += field.cost_multiplier * field.expected_count
return cost
@graphql_app.route("/graphql", methods=["POST"])
def graphql_handler():
query = request.json.get("query")
cost = estimate_query_cost(query)
if cost > MAX_COST:
return {"error": "Query too expensive"}, 429
return execute_query(query)
API テスト戦略
契約テスト
API クライアントとサーバー間の"契約"をテスト:
import pytest
from pact import Consumer, Provider
# クライアント側のテスト
def test_get_user():
(Consumer('MyClient')
.has_state('user with id 1 exists')
.upon_receiving('a request to get user 1')
.with_request('GET', '/users/1')
.will_respond_with(200, body={'id': 1, 'name': 'Alice'})
.verify())
ミューテーションテスト
エラー検出率を上げるため、API の入力を系統的に変異させ、エラー検出力を確認:
# 入力の変異例
- None
- 空文字列
- 最大値 + 1
- SQLインジェクション文字列
- XSS ペイロード
OAuth 2.0 の詳細フロー と RFC 9700 セキュリティアップデート
OAuth 2.0(RFC 6749)は、1990 年代の SAML に代わる、モダンな委譲型認証方式。RFC 9700(2024)はセキュリティ強化を加えました。
Grant Types の詳細
1. Authorization Code Flow(最も安全、推奨)
1. ユーザがアプリで「ログイン」をクリック
2. アプリがブラウザを OAuth provider(Google/GitHub等)へリダイレクト
GET https://provider.com/oauth/authorize?client_id=...&redirect_uri=...&state=xyz
3. ユーザが provider でログイン・同意
4. Provider がコード付きでアプリにリダイレクト
GET https://app.com/callback?code=abc&state=xyz
5. アプリがバックエンド で provider にコード交換リクエスト
POST https://provider.com/oauth/token
Body: code=abc, client_id=..., client_secret=...(バックエンド間通信)
6. Provider が access_token を返す
7. アプリが access_token でユーザ情報を取得
GET https://provider.com/user?access_token=...
重要な保護:
stateパラメータで CSRF 攻撃を防止client_secretはバックエンド間でのみやり取り(フロントエンドでは非公開)- RFC 9700 では
code_challenge/code_verifier(PKCE) が必須化
2. Client Credentials(マシン間通信)
POST https://provider.com/oauth/token
Body: grant_type=client_credentials&client_id=...&client_secret=...
Response: { access_token: "...", expires_in: 3600 }
用途:
- サーバ間通信(マイクロサービス間)
- スケジュール済みジョブ
- バックアップシステム
PKCE(RFC 7636)の仕組み
RFC 9700 では、シングルページアプリ(SPA)とネイティブアプリで PKCE を強制:
1. Client がランダムな code_verifier を生成(50-128 文字)
code_verifier = "e9mVobs2iwLON4TJAE...(ランダム英数字)"
2. code_verifier を SHA256 でハッシュ化
code_challenge = BASE64(SHA256(code_verifier))
3. Authorization リクエストに code_challenge を含める
GET https://provider.com/oauth/authorize?
client_id=...&code_challenge=...&code_challenge_method=S256
4. Authorization code を取得
5. Token リクエストで code_verifier を送信
POST https://provider.com/oauth/token
Body: code=..., code_verifier=..., client_id=...
6. Provider が code_verifier を SHA256 でハッシュ化し、
保存した code_challenge と一致するかを検証
攻撃者が authorization code を盗んでも、code_verifier がないと token を取得できません。
NIST SP 800-63B-3(Digital Identity Guidelines)の認証要件
National Institute of Standards and Technology (NIST) が定めるパスワード・認証のガイドラインは実務標準。
パスワードポリシー(従来の誤った方針から変更)
旧来(非推奨):
- 大文字・小文字・数字・記号の強制
- 定期的な強制変更(90 日ごと等)
NIST 推奨(最新):
- 長さ重視:最低 8 文字、理想は 12-16 文字
- 一般的な脆弱パスワードのブラックリスト
- 「123456」「password」「qwerty」等
- サイト名・ユーザ名を含む
# パスワード検証の疑似コード
def validate_password(password, blacklist, username, site_name):
if len(password) < 8:
return False, "At least 8 characters"
if password.lower() in blacklist:
return False, "Password is too common"
if username.lower() in password.lower():
return False, "Password contains username"
return True, "OK"
多要素認証(MFA)の要件
NIST では 3 種類を定義:
1. Something You Know(何かを知っている)
- パスワード
- セキュリティ質問
2. Something You Have(何かを持っている)
- スマートフォン(SMS / TOTP)
- ハードウェアキー(U2F / WebAuthn)
- スマートカード
3. Something You Are(何であるか)
- 指紋認証
- 顔認証
- 虹彩スキャン
NIST SP 800-63B Level 2 では、異なる 2 つのカテゴリの組み合わせを要求:
OK:パスワード + TOTP(知っている + 持っている)
OK:パスワード + 指紋(知っている + である)
NG:パスワード + セキュリティ質問(同じカテゴリ)
セッション管理(NIST 推奨)
トークンの寿命:
- Access Token:短命(15-60 分)
- Refresh Token:長命(7-30 日)
実装:
// ログイン成功時
{
"access_token": "eyJhbGci...", // 短命(15分)
"refresh_token": "refresh_xyz...", // 長命(7日)
"expires_in": 900 // 秒
}
// Access token が期限切れ
// Refresh token を使って新しい access token を取得
POST /oauth/token
Body: { grant_type: "refresh_token", refresh_token: "..." }
// Refresh token も期限切れ → 再ログイン
OWASP API Security Top 10(2023)
OWASP API Security Top 10 2023は、API固有の攻撃面を整理した意識向上ドキュメントです。2023版では、上位5項目のうち3つが認可に関係しており、APIでは「認証できたか」よりも「この主体が、この操作を、この対象に実行できるか」を毎回確認することが中心になります。
実務では、Top 10を脆弱性一覧として暗記するより、設計レビューの質問に変換します。
| 観点 | レビューで問うこと |
|---|---|
| オブジェクト単位の認可 | URLやbodyのIDを変えても他人の資源へ届かないか |
| プロパティ単位の認可 | 管理者用フィールドや内部メモを返していないか |
| 機能単位の認可 | 一般ユーザーが管理操作を呼べないか |
| リソース消費 | 高コストAPIにrate limit、quota、timeoutがあるか |
| API棚卸し | 古いversion、debug endpoint、未文書APIが残っていないか |
API1:2023 - Broken Object Level Authorization (BOLA)
ユーザが他人のリソースにアクセスできる脆弱性。
脆弱な例:
# API エンドポイント
@app.get("/api/users/{user_id}")
def get_user(user_id: int):
user = db.query(User).filter(User.id == user_id).first()
return user
# ユーザ 123 がログイン
GET /api/users/456 # ← ユーザ 123 が他人(456)のデータを見られる!
対策:
@app.get("/api/users/{user_id}")
def get_user(user_id: int, current_user = Depends(verify_token)):
if user_id != current_user.id: # ← 権限チェック
raise HTTPException(status_code=403, detail="Not authorized")
user = db.query(User).filter(User.id == user_id).first()
return user
API2:2023 - Broken Authentication
認証メカニズムの不備。
例:
- JWT の署名検証がない
- デフォルトクレデンシャルが残っている
- トークンの有効期限がない/無制限
API3:2023 - Broken Object Property Level Authorization
オブジェクト内の機密フィールドへのアクセス制御不備。
脆弱な例:
GET /api/users/123
Response: {
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"password_hash": "abc123...", // ← 公開してはいけない
"credit_card": "4111111...", // ← 公開してはいけない
"salary": 100000 // ← 他人には見えてはいけない
}
対策:フロントエンドモデルとバックエンド返却データを分離
class UserPublic(BaseModel):
id: int
name: str
email: str
# password_hash, credit_card, salary は含めない
@app.get("/api/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int):
user = db.query(User).filter(User.id == user_id).first()
return user # Pydantic が UserPublic フィールドのみ返却
API4:2023 - Unrestricted Resource Consumption
Rate limiting がなく、リソースを無制限に消費される。
攻撃例:
対策:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/api/data")
@limiter.limit("100/minute") # 1 分間に最大 100 リクエスト
def get_data(request: Request):
return {"data": []}
API5:2023 - Broken Function Level Authorization
ロール別に機能をコントロールできていない。
脆弱な例:
# Admin のみが削除可能な API
@app.delete("/api/users/{user_id}")
def delete_user(user_id: int):
db.query(User).filter(User.id == user_id).delete()
# ← role チェックがない!ユーザなら誰でも削除できる
対策:
@app.delete("/api/users/{user_id}")
def delete_user(user_id: int, current_user = Depends(verify_token)):
if current_user.role != "admin": # ← role チェック
raise HTTPException(status_code=403, detail="Admin only")
db.query(User).filter(User.id == user_id).delete()
API6:2023 - Unrestricted Access to Sensitive Business Flows
API リクエストの順序チェックがない(例:購入前に支払い確認を取らない)。
API7:2023 - Server-Side Request Forgery (SSRF)
API が任意の URL に HTTP リクエストを送信できる。
# 脆弱
@app.get("/api/proxy")
def proxy(url: str):
response = requests.get(url) # ← 任意の URL にアクセス
return response.text
# 攻撃例
GET /api/proxy?url=http://internal.db:5432 # ← 内部リソースにアクセス!
API8:2023 - Improper Assets Management
古い API バージョンが本番で動いている、ドキュメント化されていない API エンドポイントが存在等。
対策:
- API バージョン管理を厳格に
- すべての API を登録・ドキュメント化(OpenAPI/Swagger)
- 非推奨 API は期限付きで廃止
API9:2023 - Improper Inventory and API Versioning
API10:2023 - Unsafe Consumption of APIs
外部 API(決済、天気サービス等)のセキュリティリスク管理がない。
gRPC のセキュリティ設計
REST が JSON-based で HTTP 1.1 / 2.0 を使う一方、gRPC は Protocol Buffers(バイナリ)と HTTP/2 を使用。
TLS/SSL による暗号化
import "google.golang.org/grpc"
import "google.golang.org/grpc/credentials"
// サーバ側
lis, _ := net.Listen("tcp", ":50051")
creds, _ := credentials.NewServerTLSFromFile("cert.pem", "key.pem")
server := grpc.NewServer(grpc.Creds(creds))
// クライアント側
creds, _ := credentials.NewClientTLSFromFile("cert.pem", "serverName")
conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
Interceptor(ミドルウェア)による認証
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
// リクエストのメタデータから token を取得
md, _ := metadata.FromIncomingContext(ctx)
token := md.Get("authorization")
if !validateToken(token) {
return nil, status.Error(codes.Unauthenticated, "Invalid token")
}
return handler(ctx, req)
}
server := grpc.NewServer(
grpc.UnaryInterceptor(authInterceptor),
)
まとめ
APIは、システムの接続点であり、設計の品質が後続のすべてを決めます。REST の成熟度モデル、NIST のセキュリティガイドライン、OWASP API Top 10、gRPC のセキュリティ設計、テスト戦略まで含めて、初期設計から組み込むことが重要です。
API設計と認証・認可は別のテーマですが、境界設計として強く結びついています。後から継ぎ足すより、最初から互換性、権限境界、エラー表現を含めて考える方が長期運用では安定します。
参考文献
公式・標準
- HTTP Semantics(RFC 9110)
- NIST SP 800-63B-4
- OAuth 2.0(RFC 6749)
- RFC 9700: OAuth 2.0 Security Best Current Practice
- OWASP API Security Top 10 2023
- OWASP API Security Top 10 2023: Risks
- W3C WebAuthn Level 3
- OWASP API1:2023 Broken Object Level Authorization
- GraphQL Specification
- OpenAPI Specification
- OpenID Connect Core 1.0