OpenAPI 3.2
How PermDock emits OpenAPI 3.2 security schemes, per-operation security and x-permdock-permissions, with registered x-oai-* and x-permdock-* fallbacks for 3.1 documents, and imports OpenAPI documents into a catalog.
Status: planned
Phase: 2
Adapter phases: permdock/openapi 2, permdock openapi (CLI) 2, permdock/hono and permdock/server hooks 1, other HTTP and RPC adapter hooks 2.
What it is
OpenAPI 3.2 was released in September 2025. The changes relevant to authorization are:
- Device authorization flow as a first-class
oauth2flow, next to the existing authorization code, client credentials and implicit flows. oauth2MetadataUrlon anoauth2security scheme, pointing at the authorization server's metadata document so clients discover endpoints instead of hard-coding them.deprecatedon security schemes, so an API can phase out an API key or a legacy flow while it still works.- Security schemes referenced by URI, so one organisation-wide security scheme document can be shared across many API descriptions.
Everything else PermDock relies on already existed in 3.1: components.securitySchemes (apiKey, http, oauth2 with flows.*.scopes, openIdConnect, mutualTLS), a root-level security default, per-operation security overrides where an empty array means public, OR across array entries and AND within an object, and x-* extensions on any object. For three of the new features the OpenAPI Initiative's Extension Registry lists a pre-3.2 extension: x-oai-deprecated, x-oai-deviceAuthorization and x-oai-deviceAuthorizationUrl. There is no registered extension for oauth2MetadataUrl or for URI-referenced schemes; see OpenAPI registries.
Why it matters for PermDock
An OpenAPI document is the one artefact that gateways, SDK generators, API portals and agents all read. If a route is guarded with protect(permissions.post.delete, ...), that requirement should be visible in the document as an OAuth scope, not only in application code. The reverse direction matters too: a team that already has an OpenAPI document with scopes has a permission catalog in disguise, and PermDock should be able to import it.
OpenAPI 3.2 specifically matters for agent callers. MCP clients and A2A agents obtain tokens through OAuth; oauth2MetadataUrl tells them where, the device flow covers headless agents that cannot open a browser, and deprecated lets an API retire a scheme without breaking a fleet of agents overnight. See ADR 0014.
How PermDock uses it
Output
permdock/openapi and the CLI openapi command derive the security parts of a document from the permission definition and the routes that use protect:
components.securitySchemes.<name>.flows.<flow>.scopesis filled frompermission.scopefor every permission referenced by a route (post:delete,billing:invoice:pay). The scheme name, flow types,oauth2MetadataUrl,deviceAuthorizationanddeprecatedcome from the adapter configuration.- Each operation guarded by
protectreceivessecurity: [{ <scheme>: ['post:delete'] }]. Routes with no guard get no entry (they inherit the document default), and routes guarded by an explicitly public permission getsecurity: []. - Each guarded operation also receives
x-permdock-permissions: ['post.delete'], the permissionkeylist, so tools that do not understand scopes can still see the requirement andpermdock usagecan cross-check the document against the catalog. - Resource schemas are exported via Standard JSON Schema into
components.schemaswhen the validator supports it (see Standard Schema).
Framework hooks: hono-openapi describeRoute, @hono/zod-openapi createRoute, oRPC oo.spec(middleware, ...), trpc-to-openapi protect, @elysia/openapi detail, @fastify/swagger schemas and @nestjs/swagger decorators all accept a security fragment; the HTTP adapters and RPC adapters pass PermDock's fragment into them. Producers without such a hook (next-openapi-gen for Next.js and the other frameworks it scans, hand-written descriptions) receive the same fields as an Overlay applied at build time. Downstream, SDK generators (Hey API, Orval), docs UIs (Scalar) and gateways read the standard security fields and need nothing PermDock-specific; the pipeline and the tools in it are on the OpenAPI ecosystem page and the rule is ADR 0023.
import { createPermDock } from 'permdock/hono'
export const { permdock, protect, openapi } = createPermDock(policy, {
subject: (c) => c.get('user'),
openapi: {
target: '3.2', // '3.1' switches to the registered x-oai-* and x-permdock-* fallbacks
scheme: 'oauth',
oauth2MetadataUrl: 'https://auth.example.com/.well-known/oauth-authorization-server',
flows: ['authorizationCode', 'deviceAuthorization'],
},
})
app.delete('/posts/:id', protect(permissions.post.delete, (c) => loadPost(c.req.param('id'))), handler)
// operation: security: [{ oauth: ['post:delete'] }], x-permdock-permissions: ['post.delete']3.1 fallbacks
When target: '3.1' is selected the emitter writes the same information using extensions, choosing a registered name whenever one exists:
| 3.2 field | 3.1 fallback | Registered in the OAI Extension Registry |
|---|---|---|
flows.deviceAuthorization | x-oai-deviceAuthorization inside flows | yes |
deviceAuthorizationUrl inside that flow | x-oai-deviceAuthorizationUrl | yes |
deprecated on a security scheme | x-oai-deprecated | yes |
oauth2MetadataUrl | x-permdock-oauth2MetadataUrl | no x-oai-* extension is registered for it |
| Scheme referenced by URI | Inlined into components.securitySchemes | not applicable |
Only the three registered x-oai-* names are ever emitted; PermDock does not coin x-oai-* names of its own, because oai is the namespace reserved for the OpenAPI Initiative (Namespace Registry). Anything else PermDock needs to say lives under x-permdock-* (OpenAPI registries, ADR 0019). The scope and x-permdock-permissions output is identical in both versions. The importer reads both the native 3.2 fields and every fallback in the table.
3.3 and later
target: '3.3' is experimental and tracks the v3.3-dev branch; it currently emits the 3.2 shape plus x-permdock-securityProfile and is documented on OpenAPI 3.3. The default target stays 3.2 until 3.3 is released.
Import
permdock openapi import reads a 3.1 or 3.2 document and emits src/permissions.generated.ts, a deterministic definePermissions() call with a // @generated header. Each securitySchemes.*.scopes entry becomes a permission whose scope is the OAuth scope; the key is derived from the scope by replacing : with .; an operation's security becomes a hint that lands in the permission's meta so permdock usage can report scopes that no route requires. The generated file merges with hand-written definitions like any other feature file (see larger apps).
Mapping table
| OpenAPI 3.2 concept | PermDock concept |
|---|---|
securitySchemes.<name>.flows.*.scopes | permission.scope for every permission used by a guarded route |
Per-operation security | protect(permission, ...) on that route |
security: [] | Route guarded by a permission that every subject is granted (public) |
oauth2MetadataUrl | Adapter option; passed through to the document (or x-permdock-oauth2MetadataUrl for 3.1) |
| Device authorization flow | Adapter option flows: ['deviceAuthorization'] (or x-oai-deviceAuthorization with x-oai-deviceAuthorizationUrl) |
deprecated on a scheme | Adapter option per scheme (or x-oai-deprecated) |
| Scheme referenced by URI | Adapter option schemeRef; inlined for 3.1 |
x-permdock-permissions | Permission key list for the operation |
components.schemas | Standard JSON Schema export of resource schemas |
Imported scopes | permissions.generated.ts leaves with scope set |
Sources
- OpenAPI Specification 3.2.0 release.
- OAI Extension Registry and Namespace Registry.
- Landscape research, "OpenAPI 3.1 security" notes and the
hono-openapi,@hono/zod-openapi, oRPCoo.specandtrpc-to-openapihook comparison; OpenAPI ecosystem research for producers without a hook, appliers and consumers. - ADR 0014: target OpenAPI 3.2 and ADR 0019: extensions and Overlay.
Related
- OpenAPI registries: the
x-permdock-*namespace and the registered names PermDock reuses. - OpenAPI Overlay:
permdock openapi --format overlay. - OpenAPI 3.3: what is tracked for the next minor.
- Arazzo workflows: pre-flighting a workflow against
x-permdock-permissions.
Open questions
- The exact shape of the
openapiadapter options; the option names in the example above (scheme,flows,schemeRef) are illustrative and will be fixed when thepermdock/openapientry is implemented.targetmirrors the CLI--targetflag. - Whether a permission with a portable condition should be represented in the document at all (a scope cannot express
authorId = subject.id); the current plan emits the scope and leaves the condition tox-permdock-permissionsconsumers. - Whether import should also read
x-permdock-permissionsto round-trip keys exactly, or only derive keys from scopes. - Validating emitted documents against the official 3.2 JSON Schema in CI, and whether
permdock doctorshould do the same locally.
Standard Schema
How PermDock consumes Standard Schema v1 and Standard JSON Schema so resources can be defined with Zod, Valibot, ArkType or Effect Schema without adapters.
OpenAPI 3.3
What OpenAPI 3.3 contains today (v3.3-dev is still the 3.2 text; Security Profiles exist as OpenAPI Initiative Discussion #5304), the exact shape --target 3.3 emits from that pinned draft next to x-permdock-securityProfile, how the pin is recorded and checked, what happens at 3.3.0, and why GNAP is a reserved name rather than output.