-
Notifications
You must be signed in to change notification settings - Fork 0
Security in the Document
The generator documents authentication without importing the security extension. It reads two things: the scheme registry from the DI container, and interfaces on routes.
The security extension registers a rextension.SchemeRegistry in the container
during its OnInitialize; the generator resolves it in OnStart. If it is
absent — no security extension configured — nothing is documented and nothing
fails.
"components": {
"securitySchemes": {
"jwt": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Access token issued by the identity provider",
"x-roles-claim": "realm_access.roles"
},
"session": {
"type": "apiKey",
"in": "cookie",
"name": "session_id"
}
}
}| Emitted from | Read through |
|---|---|
type, description
|
SecuritySchemeAccessor |
name, in
|
ParameterizedScheme — ParamName(), Location()
|
bearerFormat |
BearerFormatProvider |
x-roles-claim |
RoleClaimProvider |
Each is optional and type-asserted, so a scheme that implements none is still documented with the basics.
Location()returns a plainstringon purpose. It used to return a named type, which is why the generator had to reach it withreflect.MethodByName("Location")and format the result with%s— a named string type cannot satisfy an interface declaringLocation() string.
A route implementing SecuredRouteAccessor gets a security block:
func (r *AdminRoute) RequiredSchemes() []string { return []string{"jwt"} }"security": [{"jwt": []}]func (r *AdminRoute) RequiredScopes() map[string][]string {
return map[string][]string{"jwt": {"users.write", "users.read"}}
}"security": [{"jwt": ["users.write", "users.read"]}]The scopes go into the security requirement itself, which is where OpenAPI puts them — rather than emitting an empty slice and losing the information.
Roles are not an OpenAPI concept, so they are emitted as a vendor extension:
func (r *AdminRoute) RequiredRoles() map[string][]string {
return map[string][]string{"jwt": {"admin"}}
}"x-required-roles": {"jwt": ["admin"]}rextension-swagger reads
this and renders a Required Authorization panel on the operation, so someone
reading the UI can see that an endpoint needs admin before they try it.
x-roles-claim on the scheme tells the same UI where in the token those roles
live.
Both RequiredRoles and RequiredScopes are keyed by scheme name, and each
key must appear in RequiredSchemes. The security extension makes a mismatch a
startup error;
the generator would simply emit an entry nobody enforces.
The generator resolves the registry through the rextension.SchemeRegistry
interface, from the container.
It used to read a package-level slice in
rextension, written byRegisterSecuritySchemes— which replaced rather than appended and had no unregister. Two Rex instances in one process clobbered each other's schemes, and state leaked between tests in the same binary. Same decoupling, without the process-global state.
If no security extension is registered, components.securitySchemes is absent
and no operation carries a security block. That is correct for an API with no
authentication — and a good reason to check the generated document in review:
an operation you expected to be secured that has no security block is a route
that is not secured.
rextension-openapi — OpenAPI 3.1 generation for Rex · MIT · © 2026 Kryovyx
Ecosystem