From e6685dbf412dad9a53d4f045f0d9f02b662330be Mon Sep 17 00:00:00 2001 From: kriptoburak Date: Fri, 3 Jul 2026 00:50:34 +0300 Subject: [PATCH 1/5] fix: allow anonymous security alternatives --- internal/test/issues/issue518/main.gen.go | 6 ---- internal/test/issues/issue518/main_test.go | 32 +++++++++++----------- internal/test/issues/issue518/spec.yaml | 1 + pkg/codegen/operations.go | 3 ++ 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/internal/test/issues/issue518/main.gen.go b/internal/test/issues/issue518/main.gen.go index a7b64d5808..abac87657f 100644 --- a/internal/test/issues/issue518/main.gen.go +++ b/internal/test/issues/issue518/main.gen.go @@ -7,10 +7,6 @@ import ( "github.com/gofiber/fiber/v2" ) -const ( - BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes" -) - // bearerAuthContextKey is the context key for bearerAuth security scheme type bearerAuthContextKey string @@ -36,8 +32,6 @@ type HandlerMiddlewareFunc func(c *fiber.Ctx, next fiber.Handler) error // AuthCheck operation middleware func (siw *ServerInterfaceWrapper) AuthCheck(c *fiber.Ctx) error { - c.Context().SetUserValue((BearerAuthScopes), []string{}) - handler := func(c *fiber.Ctx) error { return siw.Handler.AuthCheck(c) } diff --git a/internal/test/issues/issue518/main_test.go b/internal/test/issues/issue518/main_test.go index f1d7471c1b..de47f40f0f 100644 --- a/internal/test/issues/issue518/main_test.go +++ b/internal/test/issues/issue518/main_test.go @@ -3,6 +3,10 @@ package issue518 import ( "net/http" "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strings" "testing" "github.com/gofiber/fiber/v2" @@ -21,14 +25,6 @@ func (i *impl) Test(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } -// hasSecurityScopes returns true if the BearerAuthScopes key was set in context, -// even if the scopes slice is empty (an empty slice means the security scheme is -// defined on the operation with no required scopes, which still requires auth). -func hasSecurityScopes(c *fiber.Ctx) bool { - _, ok := c.Context().UserValue(BearerAuthScopes).([]string) - return ok -} - func TestIssue518(t *testing.T) { server := &impl{} @@ -47,23 +43,17 @@ func TestIssue518(t *testing.T) { }, HandlerMiddlewares: []HandlerMiddlewareFunc{ func(c *fiber.Ctx, next fiber.Handler) error { - if hasSecurityScopes(c) && c.Get(fiber.HeaderAuthorization) == "" { - return c.SendStatus(fiber.StatusUnauthorized) - } return next(c) }, }, }) }) - t.Run("secured endpoint requires auth when scopes are present", func(t *testing.T) { + t.Run("endpoint with anonymous security alternative allows missing auth", func(t *testing.T) { r := fiber.New() RegisterHandlersWithOptions(r, server, FiberServerOptions{ HandlerMiddlewares: []HandlerMiddlewareFunc{ func(c *fiber.Ctx, next fiber.Handler) error { - if hasSecurityScopes(c) && c.Get(fiber.HeaderAuthorization) == "" { - return c.SendStatus(fiber.StatusUnauthorized) - } return next(c) }, }, @@ -72,7 +62,7 @@ func TestIssue518(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/auth-check", nil) resp, err := r.Test(req) assert.NoError(t, err) - assert.Equal(t, fiber.StatusUnauthorized, resp.StatusCode) + assert.Equal(t, fiber.StatusOK, resp.StatusCode) req = httptest.NewRequest(http.MethodGet, "/auth-check", nil) req.Header.Set(fiber.HeaderAuthorization, "Bearer token") @@ -86,3 +76,13 @@ func TestIssue518(t *testing.T) { assert.Equal(t, fiber.StatusOK, resp.StatusCode) }) } + +func TestIssue518AnonymousSecurityAlternativeDoesNotEmitScopes(t *testing.T) { + _, testFile, _, ok := runtime.Caller(0) + assert.True(t, ok) + + generatedPath := filepath.Join(filepath.Dir(testFile), "main.gen.go") + generated, err := os.ReadFile(generatedPath) + assert.NoError(t, err) + assert.False(t, strings.Contains(string(generated), "SetUserValue")) +} diff --git a/internal/test/issues/issue518/spec.yaml b/internal/test/issues/issue518/spec.yaml index da011a7eee..39cf6ee2f9 100644 --- a/internal/test/issues/issue518/spec.yaml +++ b/internal/test/issues/issue518/spec.yaml @@ -9,6 +9,7 @@ paths: get: operationId: authCheck security: + - {} - bearerAuth: [] responses: 200: diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 55e512a669..3924b43efd 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -300,6 +300,9 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen outDefs := make([]SecurityDefinition, 0) for _, sr := range securityRequirements { + if len(sr) == 0 { + return nil + } for _, k := range SortedMapKeys(sr) { v := sr[k] outDefs = append(outDefs, SecurityDefinition{ProviderName: k, Scopes: v}) From 779e8a2f08fc17cb7cd11c824844b15d7619e91e Mon Sep 17 00:00:00 2001 From: kriptoburak Date: Fri, 3 Jul 2026 01:00:20 +0300 Subject: [PATCH 2/5] test: require generated fixture read --- internal/test/issues/issue518/main_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/test/issues/issue518/main_test.go b/internal/test/issues/issue518/main_test.go index de47f40f0f..682b13d73d 100644 --- a/internal/test/issues/issue518/main_test.go +++ b/internal/test/issues/issue518/main_test.go @@ -11,6 +11,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type impl struct{} @@ -83,6 +84,6 @@ func TestIssue518AnonymousSecurityAlternativeDoesNotEmitScopes(t *testing.T) { generatedPath := filepath.Join(filepath.Dir(testFile), "main.gen.go") generated, err := os.ReadFile(generatedPath) - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, strings.Contains(string(generated), "SetUserValue")) } From 1cd76e3e3bd8e152abcba18a024b7167e9bc35b8 Mon Sep 17 00:00:00 2001 From: kriptoburak Date: Fri, 3 Jul 2026 01:14:00 +0300 Subject: [PATCH 3/5] docs: note anonymous security alternative behavior --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c8d3d770ef..640bb2a585 100644 --- a/README.md +++ b/README.md @@ -3353,6 +3353,10 @@ If you're using a specification with [Security Schemes](https://spec.openapis.or 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 does not set +scope context for that operation. + ### On the client With a generated client, you'll want to use the client's generated `WithRequestEditorFn` function to pass in a given request editor `RequestEditorFn`. From 8f3783ab4693a6b6b278e8ea5d56da9ef9410656 Mon Sep 17 00:00:00 2001 From: Marcin Romaszewicz Date: Tue, 7 Jul 2026 09:21:38 -0700 Subject: [PATCH 4/5] docs: warn about behavior change for existing anonymous security alternatives Specs that already list an empty `{}` security alternative alongside named schemes previously still had the scheme's scopes context key (such as `BearerAuthScopes`) set by generated middleware. After regenerating, that key is no longer set for such operations, so authentication middleware that enforces auth based on its presence will treat those requests as anonymous. Call this out explicitly in the security documentation. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a6e8f7e25b..f6e3ba59f3 100644 --- a/README.md +++ b/README.md @@ -3357,6 +3357,14 @@ If an operation includes an empty `{}` security alternative, generated server middleware treats that operation as allowing anonymous requests and does not set scope context for that operation. +> [!WARNING] +> This is a behavior change for specs that already list an anonymous `{}` alternative +> alongside named schemes, in any position (e.g. `security: [{bearerAuth: []}, {}]`). +> Previously, the generated middleware still set the scheme's scopes context key (such as +> `BearerAuthScopes`); after regenerating, that key is no longer set for such operations, +> so authentication middleware that enforces auth based on its presence will treat those +> requests as anonymous. + ### On the client With a generated client, you'll want to use the client's generated `WithRequestEditorFn` function to pass in a given request editor `RequestEditorFn`. From b29f140137e45cdf46e85bb52f81f9e58da61ba9 Mon Sep 17 00:00:00 2001 From: Marcin Romaszewicz Date: Tue, 7 Jul 2026 12:02:34 -0700 Subject: [PATCH 5/5] Update Readme --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f6e3ba59f3..8914ddaf2a 100644 --- a/README.md +++ b/README.md @@ -3348,22 +3348,12 @@ If you're using a specification with [Security Schemes](https://spec.openapis.or > 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). -> -> In the future, we plan to [implement server-side validation in the generated code](https://github.com/oapi-codegen/oapi-codegen/issues/1524) 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 does not set -scope context for that operation. - -> [!WARNING] -> This is a behavior change for specs that already list an anonymous `{}` alternative -> alongside named schemes, in any position (e.g. `security: [{bearerAuth: []}, {}]`). -> Previously, the generated middleware still set the scheme's scopes context key (such as -> `BearerAuthScopes`); after regenerating, that key is no longer set for such operations, -> so authentication middleware that enforces auth based on its presence will treat those -> requests as anonymous. +middleware treats that operation as allowing anonymous requests and doesn't embed required +security scopes in the request context. ### On the client