Ian Chou's Blog

從零開始:用 Hono + Cloudflare Workers 打造 Mock OAuth Server

從零開始:用 Hono + Cloudflare Workers 打造 Mock OAuth Server

本文記錄如何建立一個輕量級的 Mock OAuth 2.0 Server,用於測試 MCP Client 或其他需要 OAuth 認證的應用程式。

為什麼需要 Mock OAuth Server?

當你在開發需要 OAuth 認證的應用程式時,常常會遇到這些問題:

  1. 測試困難:每次測試都要登入 Google/GitHub 很麻煩
  2. 無法測試異常情況:真實 OAuth Provider 不會故意回傳錯誤
  3. 本地開發受限localhost 難以被外部服務呼叫
  4. 速度慢:真實 OAuth 流程涉及多次網路請求

Mock OAuth Server 可以解決這些問題,讓你完全掌控 OAuth 流程。

技術選擇:Hono + Cloudflare Workers

我們選擇這個組合的原因:

優點 說明
公開 HTTPS 獲得 https://xxx.workers.dev 網址,外部服務可呼叫
零冷啟動 Workers 啟動速度極快,測試不會卡頓
免費 Cloudflare 免費額度對測試綽綽有餘
Hono 輕量 類似 Express 的語法,學習成本低

實作步驟

Step 1:建立專案

mkdir my-oauth-mock
cd my-oauth-mock
bun init -y

Step 2:安裝依賴

bun add hono
bun add -d wrangler @cloudflare/workers-types typescript

Step 3:設定 Wrangler (wrangler.toml)

name = "my-oauth-mock"
main = "src/index.ts"
compatibility_date = "2024-12-30"

[observability]
enabled = true

Step 4:實作 OAuth Server (src/index.ts)

核心程式碼包含四個端點:

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono()

// 開啟 CORS
app.use('/*', cors())

// 首頁
app.get('/', (c) => c.text('OAuth 2.0 Mock Server is Running! 🚀'))

// 1. Authorization Endpoint - 顯示同意頁面
app.get('/authorize', async (c) => {
  const redirectUri = c.req.query('redirect_uri')
  const state = c.req.query('state') || ''
  const clientId = c.req.query('client_id') || 'Unknown App'
  
  // 返回 HTML 同意頁面
  const html = `
    <h1>授權請求</h1>
    <p>${clientId} 想要存取您的帳戶</p>
    <form method="POST" action="/authorize">
      <input type="hidden" name="redirect_uri" value="${redirectUri}">
      <input type="hidden" name="state" value="${state}">
      <button name="action" value="allow">授權</button>
      <button name="action" value="deny">拒絕</button>
    </form>
  `
  return c.html(html)
})

// 2. 處理同意/拒絕
app.post('/authorize', async (c) => {
  const body = await c.req.parseBody()
  const redirectUri = body['redirect_uri'] as string
  const state = body['state'] as string || ''
  const action = body['action'] as string
  
  const callbackUrl = new URL(redirectUri)
  
  if (action === 'allow') {
    const code = 'mock_code_' + crypto.randomUUID().split('-')[0]
    callbackUrl.searchParams.set('code', code)
  } else {
    callbackUrl.searchParams.set('error', 'access_denied')
  }
  
  if (state) callbackUrl.searchParams.set('state', state)
  return c.redirect(callbackUrl.toString())
})

// 3. Token Endpoint
app.post('/token', async (c) => {
  return c.json({
    access_token: 'mock_access_token_' + Date.now(),
    token_type: "Bearer",
    expires_in: 3600,
    refresh_token: "mock_refresh_token_xyz",
    scope: "user:email"
  })
})

// 4. UserInfo Endpoint
app.get('/userinfo', (c) => {
  const authHeader = c.req.header('Authorization')
  
  if (authHeader?.startsWith('Bearer ')) {
    return c.json({
      sub: "1234567890",
      name: "Mock User",
      email: "[email protected]",
      email_verified: true
    })
  }
  
  return c.json({ error: "Unauthorized" }, 401)
})

// 5. OIDC Discovery
app.get('/.well-known/openid-configuration', (c) => {
  const baseUrl = new URL(c.req.url).origin
  return c.json({
    issuer: baseUrl,
    authorization_endpoint: baseUrl + '/authorize',
    token_endpoint: baseUrl + '/token',
    userinfo_endpoint: baseUrl + '/userinfo'
  })
})

export default app

部署到 Cloudflare

方法一:手動部署

npx wrangler login
npx wrangler deploy

方法二:Git 整合自動部署

  1. 將專案 push 到 GitHub
  2. 在 Cloudflare Dashboard 選擇 Workers & PagesCreate
  3. 連線到 GitHub repository
  4. 設定:
    • 組建命令bun install
    • 部署命令npx wrangler deploy

每次 push 都會自動部署!


設定自訂域名

如果你有自己的域名(例如 oauth.example.com):

  1. Workers & Pages → 選擇你的 Worker
  2. SettingsTriggersCustom Domains
  3. Add Custom Domain → 輸入子域名

Cloudflare 會自動設定 DNS 並啟用 SSL。


如何使用

環境變數設定

OAUTH_CLIENT_ID="test_client_id"
OAUTH_CLIENT_SECRET="test_client_secret"
OAUTH_AUTH_URL="https://your-worker.workers.dev/authorize"
OAUTH_TOKEN_URL="https://your-worker.workers.dev/token"
OAUTH_USERINFO_URL="https://your-worker.workers.dev/userinfo"

測試流程

  1. Authorization:瀏覽器訪問

    https://your-worker.workers.dev/authorize?client_id=MyApp&redirect_uri=http://localhost:3000/callback
    
  2. 點擊「授權」,會跳轉到:

    http://localhost:3000/callback?code=mock_code_abc123
    
  3. Exchange Token

    curl -X POST https://your-worker.workers.dev/token
  4. Get UserInfo

    curl -H "Authorization: Bearer token" https://your-worker.workers.dev/userinfo

進階:模擬異常情況

這是 Mock Server 的真正價值 - 測試 Negative Cases

1. Token 快速過期

expires_in: 5,  // 5 秒後過期

2. 模擬錯誤回應

app.post('/token', async (c) => {
  return c.json({ error: "invalid_grant" }, 400)
})

3. 模擬網路延遲

app.post('/token', async (c) => {
  await new Promise(r => setTimeout(r, 5000))  // 5 秒延遲
  // ...
})

結語

透過 Hono + Cloudflare Workers,我們可以在幾分鐘內建立一個功能完整的 Mock OAuth Server。這對於:

都非常有幫助。

完整程式碼:GitHub Repository