Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3347,13 +3347,28 @@ If you're using a specification with [Security Schemes](https://spec.openapis.or
> [!NOTE]
> Out-of-the-box, the server-side code generated by `oapi-codegen` does not provide security validation.
>
> To perform authentication, you will need to use the [validation middleware](#requestresponse-validation-middleware).
> To perform authentication, you will need to use the [validation middleware](#requestresponse-validation-middleware), which validates each request against the spec's security requirements and calls your `AuthenticationFunc` with the scheme name and required scopes.

To see how this can work, check out the [authenticated API example](examples/authenticated-api/echo).

If an operation includes an empty `{}` security alternative, generated server
middleware treats that operation as allowing anonymous requests and doesn't embed required
security scopes in the request context.
#### Deprecated: auth scopes on the request context

Historically, generated server code embedded each operation's security scopes into the request context:
a context key type per security scheme (e.g. `bearerAuthContextKey`), a scope constant (e.g. `BearerAuthScopes`),
and a per-operation call storing the operation's scopes into the request context.

This mechanism is deprecated and no longer generated by default. It flattens the
spec's `security` requirements into a per-scheme list of scopes, and cannot represent
alternative schemes (OR), combined schemes (AND), or anonymous (`{}`) alternatives, so middleware built on it can't correctly enforce the spec. Use the [validation middleware](#requestresponse-validation-middleware) instead. Please see [#1524](https://github.com/oapi-codegen/oapi-codegen/issues/1524) for more background.

If you have existing middleware that relies on these constants, you can re-enable them with:

```yaml
compatibility:
enable-auth-scopes-on-context: true
```

Note that even when re-enabled, an operation that includes an empty `{}` security alternative allows anonymous requests, and no scopes are embedded into the request context for that operation.

### On the client

Expand Down
4 changes: 4 additions & 0 deletions configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@
"headers-implicitly-required": {
"type": "boolean",
"description": "Treats all response headers as required, ignoring the `required` property from the header definition. Prior to v2.6.0, oapi-codegen generated all response headers as direct values (implicitly required). The OpenAPI specification defaults headers to optional (required: false), so the corrected behavior generates optional headers as pointers. Set this to true to restore the old behavior where all headers are treated as required.\nPlease see https://github.com/oapi-codegen/oapi-codegen/issues/2267"
},
"enable-auth-scopes-on-context": {
"type": "boolean",
"description": "Re-enables the legacy emission of security scheme scopes by generated server code: the per-scheme context key types (e.g. `bearerAuthContextKey`), the scope constants (e.g. `BearerAuthScopes`), and the per-operation calls that store the operation's scopes into the request context.\nThis mechanism is deprecated and off by default: it flattens the OpenAPI `security` requirements into a per-scheme list of scopes, and cannot represent alternative schemes (OR), combined schemes (AND), or anonymous (`{}`) alternatives. Authentication and authorization should instead be performed at runtime using the request validation middleware, which evaluates the spec's security requirements directly.\nPlease see https://github.com/oapi-codegen/oapi-codegen/issues/1524"
}
}
},
Expand Down
11 changes: 0 additions & 11 deletions examples/authenticated-api/echo/api/api.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 0 additions & 19 deletions examples/authenticated-api/stdhttp/api/api.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 0 additions & 15 deletions internal/test/aggregates/anyof/anyof.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions internal/test/clients/clients.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 0 additions & 13 deletions internal/test/naming/identifiers/identifiers_schemas.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 0 additions & 11 deletions internal/test/references/multipackage/petstore/petstore.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions internal/test/servers/middleware/fiber/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ package: serversmiddlewarefiber
generate:
fiber-server: true
models: true
# Opt into the deprecated scope-emission behavior so the issue-518 regression
# stays meaningful: even with scopes enabled, an operation with an anonymous
# `{}` security alternative must not embed scopes into the request context.
compatibility:
enable-auth-scopes-on-context: true
output: middleware.gen.go
9 changes: 6 additions & 3 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -926,9 +926,12 @@ func collectComponentTypes(t *template.Template, swagger *openapi3.T, excludeSch
if err != nil {
return nil, fmt.Errorf("error generating Go types for component request bodies: %w", err)
}
securitySchemeTypes, err := GenerateTypesForSecuritySchemes(t, swagger.Components.SecuritySchemes)
if err != nil {
return nil, fmt.Errorf("error generating Go types for component security schemes: %w", err)
var securitySchemeTypes []TypeDefinition
if globalState.options.Compatibility.EnableAuthScopesOnContext {
securitySchemeTypes, err = GenerateTypesForSecuritySchemes(t, swagger.Components.SecuritySchemes)
if err != nil {
return nil, fmt.Errorf("error generating Go types for component security schemes: %w", err)
}
}
allTypes := append(schemaTypes, paramTypes...)
allTypes = append(allTypes, responseTypes...)
Expand Down
53 changes: 53 additions & 0 deletions pkg/codegen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,58 @@ components:
}
}

// TestEnableAuthScopesOnContext verifies that generated server code embeds
// security scheme scopes into the request context only when the deprecated
// enable-auth-scopes-on-context compatibility option is set.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/1524
func TestEnableAuthScopesOnContext(t *testing.T) {
const spec = `
openapi: "3.0.0"
info:
version: 1.0.0
title: Test Auth Scopes Emission
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
paths:
/secured:
get:
operationId: secured
security:
- bearerAuth: ["read"]
responses:
'200':
description: ok
`
loader := openapi3.NewLoader()
swagger, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)

opts := Configuration{
PackageName: "api",
Generate: GenerateOptions{
StdHTTPServer: true,
Models: true,
},
}

// By default, no context key types, scope constants, or context values
// are generated.
code, err := Generate(swagger, opts)
require.NoError(t, err)
assert.NotContains(t, code, "BearerAuthScopes")
assert.NotContains(t, code, "bearerAuthContextKey")
assert.NotContains(t, code, "context.WithValue")

// With the compatibility option set, the legacy scope emission returns.
opts.Compatibility.EnableAuthScopesOnContext = true
code, err = Generate(swagger, opts)
require.NoError(t, err)
assert.Contains(t, code, `BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes"`)
assert.Contains(t, code, `ctx = context.WithValue(ctx, BearerAuthScopes, []string{"read"})`)
Comment on lines +493 to +514

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test coverage limited to a single backend

TestEnableAuthScopesOnContext only exercises StdHTTPServer. All seven backend templates (chi, echo, echo/v5, fiber, gin, gorilla, iris) had guard changes in this PR, and any future template-level regression (e.g., the if/range ordering in echo-wrappers.tmpl) would go undetected. At minimum, adding EchoServer: true as a second sub-test case (reusing the same spec) would catch divergence between the chi/gorilla/stdhttp pattern (if and … opts.Compatibility) and the echo/gin/iris/fiber pattern (outer if opts.Compatibility, inner range) that are structurally different in the templates.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/codegen/codegen_test.go
Line: 493-514

Comment:
**Test coverage limited to a single backend**

`TestEnableAuthScopesOnContext` only exercises `StdHTTPServer`. All seven backend templates (chi, echo, echo/v5, fiber, gin, gorilla, iris) had guard changes in this PR, and any future template-level regression (e.g., the `if`/`range` ordering in `echo-wrappers.tmpl`) would go undetected. At minimum, adding `EchoServer: true` as a second sub-test case (reusing the same spec) would catch divergence between the chi/gorilla/stdhttp pattern (`if and … opts.Compatibility`) and the echo/gin/iris/fiber pattern (outer `if opts.Compatibility`, inner `range`) that are structurally different in the templates.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

}

//go:embed test_spec.yaml
var testOpenAPIDefinition string
14 changes: 14 additions & 0 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,20 @@ type CompatibilityOptions struct {
// are treated as required.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/2267
HeadersImplicitlyRequired bool `yaml:"headers-implicitly-required,omitempty"`

// EnableAuthScopesOnContext re-enables the legacy emission of security
// scheme scopes by generated server code: the per-scheme context key
// types (e.g. `bearerAuthContextKey`), the scope constants (e.g.
// `BearerAuthScopes`), and the per-operation calls that store the
// operation's scopes into the request context.
// This mechanism is deprecated and off by default: it flattens the
// OpenAPI `security` requirements into a per-scheme list of scopes, and
// cannot represent alternative schemes (OR), combined schemes (AND), or
// anonymous (`{}`) alternatives. Authentication and authorization should
// instead be performed at runtime using the request validation
// middleware, which evaluates the spec's security requirements directly.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/1524
EnableAuthScopesOnContext bool `yaml:"enable-auth-scopes-on-context,omitempty"`
}

func (co CompatibilityOptions) Validate() map[string]string {
Expand Down
Loading