Skip to main content

OpenAPI generator

WIP

authBuildOpenApiSpec + authRenderOpenApiYaml - emit an OpenAPI 3.1 spec for the mounted auth routes.

What it does

duck-auth's OpenAPI generator introspects your AuthEngine instance and emits an OpenAPI 3.1 specification for every mounted route - /signin, /signout, /session, and one /providers/:providerId/begin per registered provider. The spec includes request schemas, response schemas, every typed AuthError code, and security schemes.

Use cases:

  • Generate a typed client SDK for partners.
  • Drive contract tests in CI.
  • Render an interactive API console (Swagger UI / Stoplight) for your integrators.
  • Validate request shapes at the gateway before they reach your service.

Generate from CLI

The simplest path:

bunx @gentleduck/auth emit-openapi --out openapi.yaml

The CLI loads your auth.ts, calls authBuildOpenApiSpec, and writes the result to disk.

Flags:

  • --out <path> - file destination. Default: stdout.
  • --format yaml|json - serialisation. Default: yaml.
  • --prefix <path> - route prefix (default /auth).
  • --include <pattern> - only operations matching this glob.

Generate programmatically

import { authBuildOpenApiSpec, authRenderOpenApiYaml } from '@gentleduck/auth/openapi'
import { auth } from './lib/auth'

const spec = authBuildOpenApiSpec({
  baseUrl: 'https://api.example.com',
  title: 'Example Auth',
  version: '1.0.0',
  prefix: '/auth',
  providers: ['password', 'magic-link', 'google', 'github', 'passkey'],
})

console.log(authRenderOpenApiYaml(spec))

Or write to disk:

import { writeFile } from 'node:fs/promises'

const yaml = authRenderOpenApiYaml(spec)
await writeFile('openapi.yaml', yaml, 'utf-8')

What's in the spec

The generated spec includes:

Paths

PathMethods
/auth/signinPOST
/auth/signoutPOST
/auth/sessionGET
/auth/providers/{providerId}/beginPOST
/auth/providers/{providerId}/callbackGET (OAuth callback)
/.well-known/openid-configurationGET (if OIDC discovery enabled)
/.well-known/jwks.jsonGET (if JwtTransport with asym keys)

Schemas

  • Session - sid, expiresAt, aal, amr, kind, identityId.
  • Identity - id, email, emailVerified, profile (typed by your Profile generic).
  • AuthError - code, status, meta. One oneOf entry per AUTH/* code.
  • Per-provider input schemas - PasswordSignInInput, MagicLinkSignInInput, OAuthSignInInput, etc.

Security schemes

  • cookieAuth - apiKey in cookie named after your CookieTransport.
  • bearerAuth - http with bearer scheme.
  • jwtAuth - http with bearer and bearerFormat: JWT.
  • apiKeyAuth - apiKey in header named X-API-Key.

Each operation references the security schemes that apply.

Driving a typed SDK

A common workflow:

# 1. Emit the spec
bunx @gentleduck/auth emit-openapi --out openapi.yaml

# 2. Generate a TypeScript client (using openapi-typescript)
bunx openapi-typescript openapi.yaml -o sdk/types.ts

# 3. Use it
import type { paths } from './sdk/types'

type SignInResponse = paths['/auth/signin']['post']['responses']['200']['content']['application/json']

For richer codegen (operations as fetch wrappers), try openapi-fetch or oazapfts on top of the same openapi.yaml.

Customising the spec

Override or add fields after authBuildOpenApiSpec:

const spec = authBuildOpenApiSpec({ baseUrl: '...', title: 'My API' })

spec.info.contact = { name: 'API team', email: 'api@example.com' }
spec.info.license = { name: 'MIT', url: 'https://opensource.org/license/mit' }
spec.servers = [
  { url: 'https://api.example.com', description: 'Production' },
  { url: 'https://staging.example.com', description: 'Staging' },
]

// Add a custom security requirement to all operations
for (const path of Object.values(spec.paths)) {
  for (const op of Object.values(path)) {
    if (typeof op === 'object' && 'security' in op) {
      op.security = [...(op.security ?? []), { 'rate-limit-key': [] }]
    }
  }
}

The shape conforms to OpenAPI 3.1, so any spec-aware tooling consumes it without translation.