Hono
WIPWriting the auth routes on Hono over auth.flows, giving sessions the client IP, and guarding your own routes
import { AuthError } from '@gentleduck/auth/core'
import { errorResponse, executeIntents, jsonResponse, readBodyJson } from '@gentleduck/auth/server/generic'
import { honoCaller, honoCsrf } from '@gentleduck/auth/server/hono'
import { type Context, Hono } from 'hono'
import { getConnInfo } from 'hono/bun'
const app = new Hono()
const caller = (c: Context) => honoCaller({ ip: getConnInfo(c).remote.address, req: c.req })
app.onError((err) => errorResponse(err))
app.use('/auth/*', honoCsrf(auth))
app.post('/auth/signin', async (c) => {
const body = await readBodyJson(c.req.raw)
if (typeof body !== 'object' || body === null) throw new AuthError('AUTH_INVALID_PARAMETERS')
const providerId: unknown = Reflect.get(body, 'providerId')
if (typeof providerId !== 'string') throw new AuthError('AUTH_INVALID_PARAMETERS')
const { intents } = await auth.flows.signIn({
input: Reflect.get(body, 'input') ?? {},
providerId,
...caller(c),
previousSid: auth.transport.extract(c.req.raw) ?? undefined,
})
return executeIntents(intents)
})
app.post('/auth/signout', async (c) => {
const sid = auth.transport.extract(c.req.raw)
return executeIntents(sid ? (await auth.flows.signOut(sid)).intents : auth.transport.revoke())
})
app.get('/auth/session', async (c) => {
const resolved = await auth.resolveSession(c.req.raw).orNull()
if (!resolved) return jsonResponse(200, { session: null, identity: null })
// `csrfHash` is server-side state; the browser holds the plaintext
const { csrfHash: _csrfHash, ...session } = resolved.session
return jsonResponse(200, { session, identity: resolved.identity })
})
export default app
- Hono resolves no client IP, so
honoCallertakes one. Use thegetConnInfofor your runtime, or read the header your proxy sets if you trust it. Without it, sessions record none. executeIntentsanswers a flow's intents as aResponse, andjsonResponsea JSON body, both withCache-Control: no-store.- The provider routes are on the OAuth page. Register the callback before the CSRF middleware: Hono runs handlers in registration order, so a route that answers first never reaches it.
- CORS is yours to mount, with
hono/cors.
Your own routes
import { honoActorContext } from '@gentleduck/auth/server/hono'
app.use('/api/*', honoCsrf(auth), honoActorContext(auth))
app.get('/api/me', async (c) => {
const resolved = await auth.resolveSession({ headers: c.req.raw.headers }).orNull()
if (!resolved) return c.json({ error: 'unauthenticated' }, 401)
return c.json(resolved.identity)
})
honoActorContext answers a refusal itself, since Hono's default error handler turns any throw into a 500.