目次
API を公開する際、避けて通れないのが認証(Authentication)だ。「誰がアクセスしているか」を確認し、適切な認可(Authorization)を行うことで、不正アクセスやデータ漏洩を防ぐ。
本稿では、API 認証の 3 つの主要方式——API Key、OAuth 2.0、JWT——の仕組みと使い分けを解説する。
1. API Key——最もシンプルな認証
API Key は、サービスが発行するランダムな文字列をリクエストに含める方式だ。
1.1 仕組み
GET /api/v1/users HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_abcdef1234567890
またはクエリパラメータ:
GET /api/v1/users?api_key=sk_live_abcdef1234567890
1.2 実装例(Node.js/Express)
const API_KEYS = new Map([
['sk_live_abc123', { userId: 'user_1', plan: 'premium' }],
['sk_live_def456', { userId: 'user_2', plan: 'free' }]
]);
function authenticateApiKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
const keyData = API_KEYS.get(apiKey);
if (!keyData) {
return res.status(401).json({ error: 'Invalid API key' });
}
req.user = keyData;
next();
}
// 使用例
app.get('/api/users', authenticateApiKey, (req, res) => {
res.json({ user: req.user });
});
1.3 メリット・デメリット
| メリット | デメリット |
|---|---|
| 実装が簡単 | キーが漏洩すると第三者に悪用される |
| クライアント側の実装不要(キーを保持するだけ) | キーのローテーションが面倒 |
| サーバー側でレート制限容易 | 有効期限の設定が一般的でない |
1.4 適用ケース
- 推奨: 内部サービス間通信、サーバー間 API 連携
- 非推奨: エンドユーザーが使用するクライアント(モバイルアプリ、SPA)
2. OAuth 2.0——第三者連携のデファクトスタンダード
OAuth 2.0 は、ユーザーが「A サービスのデータに B サービスがアクセスする」ことを許可するためのプロトコルだ。
2.1 主要なフロー
認証コードフロー(Web アプリ向け)
1. クライアント: 「認可エンドポイントへリダイレクト」
→ https://auth.example.com/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https://myapp.com/callback&
scope=read:profile
2. ユーザー: ログインして同意
3. 認可サーバー: 認可コードをリダイレクト
→ https://myapp.com/callback?code=AUTH_CODE
4. クライアント: トークンエンドポイントへリクエスト
POST /oauth/token
grant_type=authorization_code&
code=AUTH_CODE&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
5. トークンレスポンス:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4"
}
クライアントクレデンシャルフロー(サーバー間通信)
POST /oauth/token
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
scope=read:data
2.2 実装例(OAuth プロバイダー側)
const oauth2Server = require('oauth2-server');
const express = require('express');
const app = express();
// OAuth 設定
app.oauth = new oauth2Server({
model: {
// クライアント検証
getClient: async (clientId, clientSecret) => {
const client = await db.clients.findOne({ clientId });
if (!client || client.clientSecret !== clientSecret) return null;
return { grants: client.grants };
},
// ユーザー認証
getUser: async (username, password) => {
const user = await db.users.findOne({ username });
if (!user || !await verifyPassword(password, user.passwordHash)) return null;
return user;
},
// トークン生成
saveToken: async (token, client, user) => {
await db.tokens.create({
accessToken: token.accessToken,
refreshToken: token.refreshToken,
expiresAt: token.accessTokenExpiresAt,
clientId: client.id,
userId: user.id
});
return token;
}
}
});
// 認可エンドポイント
app.get('/oauth/authorize', async (req, res) => {
// ユーザーに同意画面を表示
});
// トークンエンドポイント
app.post('/oauth/token', async (req, res) => {
try {
const token = await app.oauth.token(req, res);
res.json(token);
} catch (e) {
res.status(400).json({ error: e.message });
}
});
2.3 メリット・デメリット
| メリット | デメリット |
|---|---|
| パスワードを共有せずにアクセス権を委譲可能 | 実装が複雑 |
| スコープで細かく権限制限 | 認可サーバーの運用コスト |
| トークンの有効期限・更新機制が標準装備 | フローの選択を誤るとセキュリティリスク |
2.4 適用ケース
- 推奨: 第三者サービスとの連携、マルチテナント API
- 非推奨: 単一クライアントの内部 API
3. JWT(JSON Web Token)——ステートレスな認証
JWT は、署名付きの JSON トークンにクレーム(主張)を埋め込む方式だ。
3.1 JWT の構造
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
| セクション | 内容 |
|---|---|
| Header | アルゴリズム(HS256, RS256 など)とトークンタイプ |
| Payload | サブジェクト(ユーザー ID)、有効期限、スコープ等 |
| Signature | Header + Payload の署名(改ざん検出) |
3.2 JWT の検証
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;
function authenticateJWT(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'Token required' });
}
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
}
// トークン発行(ログイン時)
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await validateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{
sub: user.id,
name: user.name,
scope: ['read:profile', 'write:posts']
},
JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token, expiresIn: 3600 });
});
3.3 リフレッシュトークンによる更新機制
アクセストークンの有効期限を短くし、リフレッシュトークンで更新するパターン。
// リフレッシュトークン発行
function issueRefreshToken(userId) {
return jwt.sign(
{ sub: userId, type: 'refresh' },
REFRESH_SECRET,
{ expiresIn: '30d' }
);
}
// リフレッシュエンドポイント
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
jwt.verify(refreshToken, REFRESH_SECRET, (err, payload) => {
if (err || payload.type !== 'refresh') {
return res.status(403).json({ error: 'Invalid refresh token' });
}
// 新しいアクセストークンを発行
const newAccessToken = jwt.sign(
{ sub: payload.sub, scope: ['read:profile'] },
JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token: newAccessToken, expiresIn: 3600 });
});
});
3.4 メリット・デメリット
| メリット | デメリット |
|---|---|
| サーバー側でセッション状態を持たない(ステートレス) | トークン無効化が困難(ブラックリスト管理が必要) |
| 複数サービスで同じトークンを使える(マイクロサービス向き) | トークンサイズが大きい(1KB 程度) |
| 署名検証だけで認証可能、高速 | 秘密鍵管理が重要 |
3.5 適用ケース
- 推奨: マイクロサービス間認証、モバイルアプリ、SPA
- 非推奨: 即時無効化が必要なケース(セッション即時削除など)
4. 選択基準——どの方式を選ぶべきか
4.1 決定マトリクス
| シナリオ | 推奨方式 |
|---|---|
| 内部サービス間 API | API Key または クライアントクレデンシャル(OAuth 2.0) |
| エンドユーザー向け Web API | OAuth 2.0(認証コードフロー)+ JWT(アクセストークン) |
| モバイルアプリ | OAuth 2.0(PKCE 拡張)+ JWT |
| マイクロサービス間 | JWT(BFF パターン) |
| 簡易な Webhook 認証 | API Key + HMAC 署名 |
4.2 セキュリティチェックリスト
- 必ず HTTPS(TLS 1.2 以上)を使用
- API Key はヘッダーで送信(クエリパラメータ非推奨)
- トークンに有効期限を設定
- リフレッシュトークンは別管理(DB 保存)
- レート制限を実装
- 機密情報はログに出力しない
5. ハイブリッド構成の事例
実際のプロダクションでは、複数の方式を組み合わせることが多い。
┌─────────────────────────────────────────────────┐
│ API Gateway │
├─────────────────────────────────────────────────┤
│ /public/* → API Key(公開 API) │
│ /internal/* → JWT(マイクロサービス) │
│ /oauth/* → OAuth 2.0(認可フロー) │
│ /webhooks/* → HMAC 署名(Webhook 検証) │
└─────────────────────────────────────────────────┘
まとめ
- API Key: シンプルなサーバー間認証。実装容易だが漏洩リスクに注意
- OAuth 2.0: 第三者連携のデファクトスタンダード。フロー選択が重要
- JWT: ステートレスな認証。マイクロサービス向きだが無効化に課題
- 組み合わせ: 用途に応じて複数の方式を適切に使い分ける
認証方式の選択は、サービスのアーキテクチャとセキュリティ要件に依存する。本稿の選択基準を参考に、適切な方式を選定してほしい。
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。