Cloudflare Workers + HonoJSチュートリアル③：API Key 認証


## はじめに

前回は JWT (JSON Web Token) による認証を導入し、トークンを用いたセキュアな API アクセス方法を紹介しました。  

https://qiita.com/syukan3/items/3632f21915b7a78f1470

https://qiita.com/syukan3/items/7570d255575ebc31ca3e

今回は **API Key** を用いたシンプルな認証方法を実装します。
API Key 方式は、特定のクライアントだけに鍵を配布し、それをヘッダーなどに含めることでアクセスを制限する仕組みです。

---

## 1. API Key 認証の概要

- **API Key** はランダムに生成された文字列を使い、クライアントがリクエスト時に付与します。
- **Hono (Cloudflare Workers)** では、ミドルウェアを利用して API Key の検証を行い、正しくない場合は 401 (Unauthorized) を返却します。

### mermaid 図：API Key 認証フロー

```mermaid
flowchart LR
    Client["“クライアント”"]
    Request["“API Key付きリクエスト”"]
    Hono["“Honoアプリ [ミドルウェアで検証]”"]
    AuthResult["“認可 or 401エラー”"]

    Client --> Request --> Hono --> AuthResult
```

---

## 2. 環境変数 (.env) の導入

API Key をソースコード内に直書きすると、漏洩リスクが高まります。  
`.dev.vars` ファイルにキーを記述し、公開リポジトリには含めない運用にしましょう。

```bash
touch .dev.vars
```

`.env` ファイルの例:

```
API_KEY=my-very-secret-api-key
PORT=3000
```

`.gitignore` に `.dev.vars` を追加し、リポジトリに含めないようにします。

---

## 3. ディレクトリ構成

前回までのチュートリアルと同様に、認証用のロジックは `middlewares/` に配置すると整理しやすいです。

```
my-hono-crud/
├─ .dev.vars
├─ package.json
├─ tsconfig.json
├─ src/
│   ├─ index.ts           // エントリーポイント
│   ├─ middlewares/
│   │   └─ apiKeyAuth.ts  // API Key検証ミドルウェア
│   └─ users/
│       └─ index.ts
└─ ...
```

---

## 4. コード例

### 4-1. src/middlewares/apiKeyAuth.ts

API Key を検証するミドルウェアを作成します。以下の例では、`Authorization` ヘッダーに `Bearer <YOUR_API_KEY>` の形式でキーが渡される想定です。

```typescript
import { Context, Next } from 'hono'
import 'dotenv/config'

export const apiKeyAuthMiddleware = async (c: Context, next: Next) => {
  const authHeader = c.req.header('Authorization') // "Bearer xxx" が入る想定

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return c.json({ error: 'No or invalid API Key header' }, 401)
  }

  const apiKey = authHeader.replace('Bearer ', '')

  // 環境変数のAPI_KEYと一致するか確認
  if (apiKey !== c.env.API_KEY) {
    return c.json({ error: 'Invalid API Key' }, 401)
  }

  // OK なら次へ
  await next()
}
```

### 4-2. src/users/index.ts

ユーザー CRUD のエンドポイントへアクセスする前に、上記の `apiKeyAuthMiddleware` を適用します。

```typescript
import { Hono } from 'hono'
import { apiKeyAuthMiddleware } from '../middlewares/apiKeyAuth'

type User = { id: number; name: string }
let users: User[] = []

const userApp = new Hono()

// すべてのエンドポイントに対してAPI Key認証を要求
userApp.use('*', apiKeyAuthMiddleware)

// Create: ユーザー作成 (POST /users)
userApp.post('/', async (c) => {
  const { name } = await c.req.json()
  const newUser: User = {
    id: Date.now(),
    name
  }
  users.push(newUser)
  return c.json({ message: 'User created', user: newUser })
})

// Read: 全ユーザー取得 (GET /users)
userApp.get('/', (c) => {
  return c.json(users)
})

// Read: 特定ユーザー取得 (GET /users/:id)
userApp.get('/:id', (c) => {
  const userId = parseInt(c.req.param('id'))
  const user = users.find((u) => u.id === userId)
  if (!user) {
    return c.json({ error: 'User not found' }, 404)
  }
  return c.json(user)
})

// Update: ユーザー更新 (PATCH /users/:id)
userApp.patch('/:id', async (c) => {
  const userId = parseInt(c.req.param('id'))
  const { name } = await c.req.json()
  const userIndex = users.findIndex((u) => u.id === userId)

  if (userIndex === -1) {
    return c.json({ error: 'User not found' }, 404)
  }

  users[userIndex].name = name
  return c.json({ message: 'User updated', user: users[userIndex] })
})

// Delete: ユーザー削除 (DELETE /users/:id)
userApp.delete('/:id', (c) => {
  const userId = parseInt(c.req.param('id'))
  const userIndex = users.findIndex((u) => u.id === userId)

  if (userIndex === -1) {
    return c.json({ error: 'User not found' }, 404)
  }

  const deletedUser = users.splice(userIndex, 1)[0]
  return c.json({ message: 'User deleted', user: deletedUser })
})

export default userApp
```

### 4-3. src/index.ts

メインのエントリーポイントで `userApp` を呼び出します。

```typescript
import { Hono } from 'hono'
import userApp from './users/index'

const app = new Hono()

// "/users" 以下へのルートを委譲
app.route('/users', userApp)

export default app
```

---

## 5. 動作確認

ローカルで `npm start` (あるいは `wrangler dev`) などを実行し、以下のコマンドでテストしてみましょう。

### 5-1. ユーザー作成 (POST /users)

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer my-very-secret-api-key" \
  -d '{"name":"Alice"}' \
  http://localhost:8787/users
```

- `-H "Authorization: Bearer <API_KEY>"` の形で API Key を指定します。  
- `.env` の `API_KEY=my-very-secret-api-key` と一致しない場合は、`401 Unauthorized` が返されます。

### 5-2. ユーザー取得 (GET /users, GET /users/:id)

```bash
curl -X GET \
  -H "Authorization: Bearer my-very-secret-api-key" \
  http://localhost:8787/users
```

その他、PATCH / DELETE なども同様に `Authorization` ヘッダーを付与することでアクセス可能です。

---

## 6. まとめと応用

- **API Key 方式** はシンプルで実装しやすいですが、鍵の漏洩リスクに注意が必要です。
- **環境変数 (.env)** によるキー管理を行い、ソースコードに直書きしないようにしましょう。
- **鍵の定期的なローテーション** や、使用期限を設ける運用なども検討するとより安全です。
- 本番運用では、HTTPS (SSL/TLS) 化した環境で運用し、通信経路を暗号化しておきましょう。

これで **API Key 認証** を導入した Hono アプリのチュートリアルは完了です。  
今までに紹介した **JWT 認証** と **API Key 認証** は用途によって使い分けることが多いので、運用シーンに応じて最適な方法を選んでみてください。

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/