認証

Bearer Token / Basic Auth / JWT / Cookie / OAuth まで、Hono の組み込みミドルウェアと自作のパターン。

Basic 認証

import { basicAuth } from "hono/basic-auth"

app.use("/admin/*", basicAuth({
  username: "admin",
  password: "secret",
}))

複数ユーザー

app.use("/admin/*", basicAuth(
  { username: "alice", password: "..." },
  { username: "bob",   password: "..." },
))

動的検証

app.use("/admin/*", basicAuth({
  verifyUser: async (username, password, c) => {
    const user = await c.env.DB
      .prepare("SELECT * FROM users WHERE name = ?")
      .bind(username).first()
    if (!user) return false
    return await bcrypt.verify(password, user.password_hash)
  },
}))

Bearer 認証

import { bearerAuth } from "hono/bearer-auth"

app.use("/api/*", bearerAuth({ token: "secret-token" }))

// 動的検証
app.use("/api/*", bearerAuth({
  verifyToken: async (token, c) => {
    return await isValidToken(token)
  },
}))

JWT

発行

import { sign } from "hono/jwt"

app.post("/login", async (c) => {
  const { email, password } = await c.req.json()
  const user = await verifyCreds(email, password)
  if (!user) return c.text("Unauthorized", 401)

  const token = await sign({
    sub: user.id,
    email: user.email,
    exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24,   // 24h
  }, c.env.JWT_SECRET)

  return c.json({ token })
})

検証ミドルウェア

import { jwt } from "hono/jwt"

app.use("/api/*", jwt({ secret: c.env.JWT_SECRET }))

// ハンドラ内でペイロードを参照
app.get("/api/me", (c) => {
  const payload = c.get("jwtPayload")
  return c.json(payload)
})

クッキーから JWT を読む

app.use("/api/*", jwt({
  secret: c.env.JWT_SECRET,
  cookie: "auth_token",          // Authorization ではなく Cookie から
}))

アルゴリズム指定

app.use("/api/*", jwt({
  secret: PUBLIC_KEY,
  alg: "RS256",                  // 既定は HS256
}))

Cookie でセッション

import { setCookie, getCookie, deleteCookie } from "hono/cookie"

app.post("/login", async (c) => {
  const { email, password } = await c.req.json()
  const user = await verifyCreds(email, password)
  if (!user) return c.text("Unauthorized", 401)

  const sessionId = crypto.randomUUID()
  await c.env.KV.put(`session:${sessionId}`, JSON.stringify({ userId: user.id }), {
    expirationTtl: 60 * 60 * 24 * 7,   // 1週間
  })

  setCookie(c, "session", sessionId, {
    httpOnly: true,
    secure: true,
    sameSite: "Lax",
    maxAge: 60 * 60 * 24 * 7,
    path: "/",
  })

  return c.json({ ok: true })
})

app.use("/api/*", async (c, next) => {
  const sid = getCookie(c, "session")
  if (!sid) return c.text("Unauthorized", 401)
  const data = await c.env.KV.get(`session:${sid}`, "json")
  if (!data) return c.text("Unauthorized", 401)
  c.set("user", data)
  await next()
})

app.post("/logout", async (c) => {
  const sid = getCookie(c, "session")
  if (sid) await c.env.KV.delete(`session:${sid}`)
  deleteCookie(c, "session")
  return c.json({ ok: true })
})

署名付き Cookie

DB を介さず Cookie 自体に情報を入れる場合は署名必須。

import { setSignedCookie, getSignedCookie } from "hono/cookie"

await setSignedCookie(c, "user_id", "123", c.env.SECRET, {
  httpOnly: true,
  secure: true,
})

const value = await getSignedCookie(c, c.env.SECRET, "user_id")
// 改ざんされていれば false が返る

OAuth2(Google / GitHub 等)

@hono/oauth-providers パッケージで主要プロバイダに対応:

npm install @hono/oauth-providers
import { googleAuth } from "@hono/oauth-providers/google"

app.use("/auth/google", googleAuth({
  client_id: c.env.GOOGLE_CLIENT_ID,
  client_secret: c.env.GOOGLE_CLIENT_SECRET,
  scope: ["openid", "email", "profile"],
}))

app.get("/auth/google", (c) => {
  const user = c.get("user-google")
  // user.email, user.name, user.picture
  // セッション発行
})

他にも githubAuth / discordAuth / facebookAuth / linkedinAuth 等。

外部認証サービス

API キー認証(ヘッダー検証)

app.use("/api/*", async (c, next) => {
  const key = c.req.header("x-api-key")
  if (!key) return c.text("API key required", 401)

  const valid = await c.env.KV.get(`api_key:${key}`)
  if (!valid) return c.text("Invalid API key", 401)

  await next()
})

ロールベースアクセス制御(RBAC)

function requireRole(role: string) {
  return async (c: any, next: any) => {
    const user = c.get("user")
    if (!user.roles.includes(role)) {
      return c.text("Forbidden", 403)
    }
    await next()
  }
}

app.delete("/admin/users/:id", requireRole("admin"), handler)

CSRF 対策

import { csrf } from "hono/csrf"

app.use(csrf({
  origin: ["https://example.com"],
}))

CSRF は Cookie 認証 + state 変更 API で気をつける。Bearer Token なら基本不要。

パスワードハッシュ

Cloudflare Workers では bcrypt が動かない(Node 専用 native)。代替:

// scrypt でハッシュ
async function hashPassword(password: string, salt: Uint8Array) {
  const enc = new TextEncoder()
  const key = await crypto.subtle.importKey(
    "raw", enc.encode(password), "PBKDF2", false, ["deriveBits"]
  )
  const bits = await crypto.subtle.deriveBits(
    { name: "PBKDF2", salt, iterations: 100_000, hash: "SHA-256" },
    key, 256,
  )
  return new Uint8Array(bits)
}
認証方式の選び方

API(モバイル / SPA): Bearer + JWT、または Cookie
SSR Web(同一オリジン): Cookie + サーバセッション
外部 API キー(B2B): API Key + KV / DB
SSO 連携: OAuth プロバイダ + JWT 検証
パスワードを持ちたくない: マジックリンク / OAuth / Passkeys