Include auth middleware responses in zod-openapi reasponses
Which middleware is the feature for?
@hono/zod-openapi
What is the feature you are proposing?
I originally posted this on StackOverflow and plan to provide an answer if I can find a solution here. https://stackoverflow.com/questions/79715942/how-to-include-hono-jwt-auth-middleware-responses-in-hono-zod-openapi
I have an API built using Hono and the @hono/zod-openapi. I've integrated hono/jwt auth middleware.
My auth middleware is a thin wrapper around hono/jwt in order to supply secrets.
import { jwt } from 'hono/jwt'
import { env } from 'hono/adapter'
import { type ContextType } from '../context'
export default (c: ContextType, next: () => Promise<void>) => {
const { JWT_SECRET } = env(c)
const jwtMiddleware = jwt({ secret: JWT_SECRET })
return jwtMiddleware(c, next)
}
For a protected route I'm applying the auth middleware as follows.
app.use('/protected/*', authMiddleware)
app.route('/protected', protectedRouter)
However, I also need to create an OpenAPI definition for the 401 response that each route in the protectedRouter could return. This gives a type error since zod-openapi is unable to infer the error response from the route because it is not being explicitly returned from the route itself.
router.openapi(
createRoute({
method: 'get',
path: '/',
responses: {
200: {
description: 'List all items',
content: {
'application/json': {
schema: responseSchema(Item),
},
},
},
401: {
description: 'Unauthorized',
content: {
'application/json': {
schema: ErrorSchema,
},
},
},
},
}),
async (c) => { // <-- type error here
const db = c.get('db')
const res = await db.select().from(items)
return c.json({ data: res })
}
)
How can I include hono/jwt auth middleware responses into my hono/zod-openapi schema?
@Soviut
Can you create a minimal project to reproduce it?
I believe this should work.
The middleware should return a 401, but it does not get added to the OpenAPI doc automatically, and when I add it manually, there is a type error is VSCode claiming the route does not return a 401.
import { type Context } from 'hono'
import { OpenAPIHono } from '@hono/zod-openapi'
import { z, type ZodError } from 'zod/v4'
import { type JwtVariables, jwt } from 'hono/jwt'
import { env } from 'hono/adapter'
import { createRoute } from '@hono/zod-openapi'
export interface Bindings {
BASE_URL: string
CORS_ORIGIN: string
DATABASE_URL: string
JWT_SECRET: string
JWT_COOKIE_SECRET: string
JWT_ACCESS_TOKEN_EXPIRY: string
JWT_REFRESH_TOKEN_EXPIRY: string
}
export interface Variables extends JwtVariables {
// db: DatabaseClient
}
export interface Env {
Bindings: Bindings
Variables: Variables
}
export type ContextType = Context<Env>
export const ErrorSchema = z.object({
code: z.number().openapi({ example: 400 }),
errors: z.array(z.string()).openapi({ example: ['Bad request'] }),
})
export const formatZodErrors = (error: ZodError) =>
error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
export const ResponseSchema = <T extends z.ZodTypeAny>(item: T) =>
z.object({
data: item,
})
export const ResponseSchemaList = <T extends z.ZodTypeAny>(item: T) =>
z.object({
data: z.array(item),
})
export const organizationSchema = z.object({
id: z.string(),
name: z.string(),
})
export const createHono = () =>
new OpenAPIHono<Env>({
defaultHook: (result, c) => {
if (!result.success) {
return c.json(
{
code: 400,
errors: formatZodErrors(result.error),
},
400
)
}
},
})
const authMiddleware = (c: ContextType, next: () => Promise<void>) => {
const { JWT_SECRET } = env(c)
const jwtMiddleware = jwt({
secret: JWT_SECRET,
})
return jwtMiddleware(c, next)
}
const organizationsRouter = createHono()
organizationsRouter.openapi(
createRoute({
method: 'get',
path: '/',
responses: {
200: {
description: 'List all organizations',
content: {
'application/json': {
schema: ResponseSchemaList(organizationSchema),
},
},
},
401: {
description: 'Unauthorized',
content: {
'application/json': {
schema: ErrorSchema,
},
},
},
},
}),
async (c) => {
return c.json({ data: [ { id: 'org_123', name: 'My Organization' } ] })
}
)
const app = createHono()
app.use('/organizations/*', authMiddleware)
app.route('/organizations', organizationsRouter)
@Soviut
The code is not complete. OpenAPIHono is not exported by hono, and there is no ../context. Please provide working code:
And,
The middleware should return a 401, but it does not get added to the OpenAPI doc automatically, and when I add it manually, there is a type error is VSCode claiming the route does not return a 401.
Please describe in more detail, in an understandable way. Write the instructions to reproduce the bug. Write the expected behavior. Write the actual behavior.
@yusukebe I've updated my example with the correct imports.
If I put the 401 response in the createRoute() I get a type error on the route handler saying that it does not return a 401.
I've added comments with arrow to the lines in question.
@Soviut
If I put the 401 response in the
createRoute()I get a type error on the route handler saying that it does not return a 401.
Can you explain it with a screenshot of your editor?
@Soviut
I've added comments with arrow to the lines in question.
Where is the arrow?
@yusukebe Yes, I'll get a screenshot when I have a moment.
The arrows were added as comments to my example code above.