目次

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)、有効期限、スコープ等
SignatureHeader + 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 決定マトリクス

シナリオ推奨方式
内部サービス間 APIAPI Key または クライアントクレデンシャル(OAuth 2.0)
エンドユーザー向け Web APIOAuth 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: ステートレスな認証。マイクロサービス向きだが無効化に課題
  • 組み合わせ: 用途に応じて複数の方式を適切に使い分ける

認証方式の選択は、サービスのアーキテクチャとセキュリティ要件に依存する。本稿の選択基準を参考に、適切な方式を選定してほしい。

免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。