Hono adapter
WIPHono 4+ handlers. Runs on Bun, Node, Cloudflare Workers, Deno, and Vercel Edge.
Mount
import { Hono } from 'hono'
import {
authHonoProviderBegin,
authHonoSession,
authHonoSignIn,
authHonoSignOut,
} from '@gentleduck/auth/server/hono'
import { auth } from './lib/auth'
const app = new Hono()
app.post('/auth/signin', authHonoSignIn(auth))
app.post('/auth/signout', authHonoSignOut(auth))
app.get('/auth/session', authHonoSession(auth))
app.post('/auth/providers/:providerId/begin', (c) =>
authHonoProviderBegin(auth, c.req.param('providerId'))(c)
)
// OAuth callbacks reuse the sign-in handler.
app.get('/auth/google/callback', authHonoSignIn(auth))
app.get('/auth/github/callback', authHonoSignIn(auth))
export default app
Resolving sessions in handlers
import { AuthError } from '@gentleduck/auth/core'
app.get('/api/me', async (c) => {
const ctx = await auth.resolveSession(c.req.raw)
if (!ctx) throw new AuthError('AUTH/UNAUTHENTICATED')
return c.json(ctx.identity)
})
c.req.raw is the underlying Web-Fetch Request - duck-auth speaks
that natively, so no adapter glue is needed.
Cloudflare Workers / edge
Hono on Cloudflare Workers is well-supported, but there are two gotchas for duck-auth:
- No Node built-ins:
AuthScryptHasherusesnode:crypto. On Workers, either useAuthArgon2idHasher(peer dep), or use a custom WebCrypto-based hasher. - No
process.env: read secrets fromc.envinstead:
import { createAuth } from '~/lib/createAuth'
const app = new Hono<{ Bindings: { JWT_SECRET: string; DATABASE_URL: string } }>()
app.use('*', async (c, next) => {
c.set('auth', createAuth({
jwtSecret: c.env.JWT_SECRET,
databaseUrl: c.env.DATABASE_URL,
}))
await next()
})
app.post('/auth/signin', async (c) => {
return authHonoSignIn(c.get('auth'))(c)
})
The auth instance is built per-request from the bindings; create it
once per worker if you can amortise the cost.
Error handling
import { HTTPException } from 'hono/http-exception'
import { AuthError } from '@gentleduck/auth/core'
app.onError((err, c) => {
if (err instanceof AuthError) {
return c.json(err.toJSON(), err.status)
}
if (err instanceof HTTPException) {
return err.getResponse()
}
console.error(err)
return c.json({ error: 'internal' }, 500)
})