認証
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 等。
外部認証サービス
- Clerk — フルマネージド。Hono 連携 SDK あり
- Auth0 — JWT 検証で連携
- Supabase Auth — JWT を検証
- WorkOS — エンタープライズ SSO
- Cloudflare Access — Workers 環境では強力
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)。代替:
- bcryptjs — ピュア JS。遅い
- scrypt(Web Crypto) — 標準。CF Workers でも動く
- argon2 — WASM ビルドが必要
- Cloudflare ならむしろCloudflare Access等でパスワード自体を持たないのが楽
// 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