1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Workers + HonoJSチュートリアル②:JWT認証

Posted at

はじめに

前回は HonoJS の基礎やミドルウェアを使った機能拡張について紹介しました。

今回は、アプリケーションのセキュリティを強化するために JWT (JSON Web Token) を使用した認証方式を導入してみましょう。

JWT を使った認証は、Web API では広く採用されており、マイクロサービスや SPA (Single Page Application) など様々なシナリオで使われています。

1. 追加パッケージのインストール

まずは、JSON Web Token (JWT) を扱うためのライブラリをインストールします。

npm install jsonwebtoken @types/jsonwebtoken
  • jsonwebtoken : JWT の発行・検証に使用
  • @types/jsonwebtoken : TypeScript 用の型定義

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

本番環境を見据える場合は、JWT のシークレットキーや他の機密情報を直接ソースコードに書くのは望ましくありません。
.env ファイルを用意し、環境変数で管理するようにします。

npm install dotenv

次に .env ファイルを用意し、例えば以下のように書きます。

JWT_SECRET=my-super-secret-key
PORT=3000

.gitignore.env を追加し、公開リポジトリには含めないように注意してください。

echo '.env' >> .gitignore

3. ディレクトリ構成

前回は簡単な構成でしたが、認証や他の機能を追加していくとファイルが増えていきます。以下のようにディレクトリを整理してみましょう。

my-hono-crud/
├─ .env
├─ package.json
├─ tsconfig.json
├─ src/
│   ├─ index.ts        // エントリーポイント
│   ├─ routes/
│   │   └─ auth.ts     // 認証関連 (JWT 発行など)
│   ├─ middlewares/
│   │   └─ auth.ts     // JWT 検証用ミドルウェア
│   └─ users/
│       └─ index.ts
└─ ...

ポイントは、認証用コードとユーザー CRUD コードを分離し、さらに共通して使う JWT 検証ロジックなどは middlewares に置くことです。


4. JWT 認証の流れ

JWT 認証フローの概要を簡単な mermaid 図で示します。

  • "クライアント" が "POST /login" にユーザー名やパスワードなどの資格情報を送信
  • "Honoアプリ" が認証に成功すると、"JWTトークン発行"
  • "クライアント" は受け取った "JWTトークン" を付与して API にアクセス
  • "Honoアプリ (ミドルウェアで検証)" がトークンの妥当性をチェックし、OK なら処理を継続、NG ならエラー (401 など) を返却

5. コード例

5-1. src/routes/auth.ts (ログイン用)

ここではユーザーのログイン処理と JWT トークンの発行を行います。認証方法 (DB 連携など) は簡略化して、ユーザー名だけで通すようにしています。
実際の実装では適切なパスワード管理や認証基盤を用意してください。

import { Context } from 'hono'
import jwt from 'jsonwebtoken'
import 'dotenv/config'

const JWT_SECRET = process.env.JWT_SECRET || 'default-secret'

/**
 * ユーザー認証 (仮) & JWT トークン発行
 */
export const loginHandler = async (c: Context) => {
  const { username } = await c.req.json()
  // ここでは単純に "admin" ユーザーだけを通す例
  if (username !== 'admin') {
    return c.json({ error: 'Invalid credentials' }, 401)
  }

  // JWT を発行
  const token = jwt.sign({ username }, JWT_SECRET, {
    expiresIn: '1h', // トークンの有効期限 (例: 1時間)
  })

  return c.json({ message: 'Login success', token })
}

5-2. src/middlewares/auth.ts (JWT 検証用)

すべての保護ルート (CRUD エンドポイントなど) にアクセスする前に、このミドルウェアで JWT を検証します。

import { Context, Next } from 'hono'
import jwt from 'jsonwebtoken'
import 'dotenv/config'

const JWT_SECRET = process.env.JWT_SECRET || 'default-secret'

export const jwtAuthMiddleware = async (c: Context, next: Next) => {
  const authHeader = c.req.header('Authorization')
  if (!authHeader) {
    return c.json({ error: 'No token provided' }, 401)
  }

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

  try {
    // トークンが正しいかを検証
    jwt.verify(token, JWT_SECRET)
    // 検証に成功したら次へ
    await next()
  } catch (err) {
    return c.json({ error: 'Invalid token' }, 401)
  }
}

5-3. src/users/index.ts (ユーザー CRUD)

認証が必要なエンドポイントとして、前回の CRUD 処理をまとめた例です。
共通で jwtAuthMiddleware を挟むことで、トークンがない/不正な場合にはアクセスを拒否するようにします。

import { Hono } from 'hono'
import { jwtAuthMiddleware } from '../middlewares/auth'

// メモリ上に保持するユーザー一覧
type User = { id: number; name: string }
let users: User[] = []

const userApp = new Hono()

// ミドルウェアを適用
userApp.use('*', jwtAuthMiddleware)

// 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

5-4. src/index.ts (エントリーポイント)

index.ts からログイン用ルート (auth.ts) とユーザー CRUD ルート (user.ts) をまとめて起動します。

import { Hono } from 'hono'
import { loginHandler } from './routes/auth'
import userApp from './users/index'
import 'dotenv/config'

const app = new Hono()

// ログイン用エンドポイント
app.post('/login', loginHandler)

// ユーザー CRUD エンドポイント
app.route('/', userApp)

export default app

6. 動作確認

6-1. ログイン (POST /login)

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"admin"}' \
  http://localhost:8787/login

レスポンス例 (正常時):

{
  "message": "Login success",
  "token": "eyJhbGciOiJI..."
}

6-2. ユーザー作成 (POST /users)

上で取得した "token" を使い、Authorization ヘッダーに "Bearer <トークン>" を設定します。

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJI..." \
  -d '{"name":"Alice"}' \
  http://localhost:8787/users

これ以降の GET /users, PATCH /users/:id, DELETE /users/:id も、同様に "Authorization" ヘッダーを付与してください。


7. まとめと今後のステップ

  • JWT 認証 を導入することで、不正アクセスを防ぎつつ API エンドポイントを保護できます。
  • 環境変数 (.env) に機密情報 (JWT シークレットなど) をまとめ、本番環境と開発環境で設定を切り替えやすくします。
  • フォルダ構成 を見直すことで、認証ロジックとビジネスロジックを分離し、可読性と保守性を高めます。

本番運用を視野に入れる場合は、さらに以下のような対策を検討するとよいでしょう。

  • HTTPS の導入 (SSL/TLS 証明書設定)
  • CSRF 対策 (フォーム送信やブラウザ処理系の認証について)
  • Rate Limiting (リクエスト数制限で DoS 対策)
  • ロギング・監視 (アクセスログやエラーログの集約)
  • DB 連携 (実際の RDB や NoSQL などへの移行)

これで、簡単な JWT 認証を組み込んだ Hono アプリのチュートリアルは完了です。ぜひ、セキュリティ周りを強化して、本番環境へステップアップしてみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?