Skip to content

Support native cookie-backed APIKeySecurity for HTTP/OpenAPI #3909

Description

@CaliLuke

Summary

Goa 3.25.3 does not appear to support expressing an HTTP APIKeySecurity scheme that is transported via request cookies in a way that is both runtime-correct and OpenAPI-correct.

We need a native Goa way to model authenticated browser routes that use a session cookie (for example __Host-ak_session) as the auth credential, instead of falling back to post-generation OpenAPI rewriting.

What we expected

Given a design like this:

var AuthSessionToken = JWTSecurity("auto_k_session_bearer", func() {
    Description("App bearer")
})

var BrowserSessionCookie = APIKeySecurity("browser_session_cookie", func() {
    Description("Browser session cookie")
})

var BrowserAuthenticatedRequest = Type("BrowserAuthenticatedRequest", func() {
    APIKey("browser_session_cookie", "browser_session", String, func() {
        Description("Opaque browser session cookie")
    })
    Token("auth", String, func() {
        Description("Bearer token")
    })
})

Method("profile", func() {
    Security(AuthSessionToken)
    Security(BrowserSessionCookie)
    Payload(BrowserAuthenticatedRequest)
    Result(User)
    HTTP(func() {
        GET("/auth/profile")
        Header("auth:Authorization")
        Cookie("browser_session:__Host-ak_session")
        Response(200)
    })
})

We expected Goa to:

  1. Generate an OpenAPI security scheme for browser_session_cookie with type: apiKey, in: cookie, name: __Host-ak_session.
  2. Generate the operation security array with cookie auth as an alternative requirement.
  3. Not duplicate the auth cookie as a normal request parameter when it is already modeled as a security scheme.
  4. Keep generated server/client code valid.

What actually happens

In 3.25.3, Goa appears to resolve APIKeySecurity locations via expr.findKey, and findKey only checks query params, headers, and body. It does not check request cookies.

Relevant source:

  • expr/helpers.go:
func findKey(exp eval.Expression, keyAtt string) (string, string) {
    switch e := exp.(type) {
    case *HTTPEndpointExpr:
        if n, exists := e.Params.FindKey(keyAtt); exists {
            return n, "query"
        } else if n, exists := e.Headers.FindKey(keyAtt); exists {
            return n, "header"
        } else if e.Body == nil {
            return "", "header"
        }
        ...
        return "", "header"
    }
    return "", ""
}

Because cookies are ignored here, the generated OpenAPI/API key scheme ends up header-bound instead of cookie-bound.

Practical consequences

In our case this caused two problems:

  1. The generated OpenAPI modeled the browser session scheme as header: Authorization instead of cookie: __Host-ak_session.
  2. When the same payload attribute was also mapped as a request cookie, generated server code ended up with duplicate locals/parameters for the same field and failed to compile.

Example shape from generated code:

func NewBeginGithubLinkBrowserAuthenticatedOAuthLinkStartRequest(body *BeginGithubLinkRequestBody, auth *string, browserSession *string, browserSession *string) *restauth.BrowserAuthenticatedOAuthLinkStartRequest

and similarly duplicate local declarations in generated HTTP decode code.

Current workaround

We had to keep the DSL/browser payloads using normal cookie request fields and then post-process the generated OpenAPI artifacts to:

  1. add a cookie-based security scheme,
  2. add cookie auth as an alternative security requirement on browser-authenticated operations,
  3. remove the duplicate cookie request parameter,
  4. normalize the 401 description away from bearer-specific wording.

That gets us a truthful served OpenAPI contract, but it is obviously not ideal.

Request

Could Goa support a native way to express cookie-backed APIKeySecurity for HTTP endpoints?

Concretely, it would help if:

  1. APIKeySecurity location inference considered HTTP cookies.
  2. Cookie-backed security fields could be mapped without generating a duplicate cookie parameter.
  3. Generated OpenAPI and generated transport code remained aligned for this case.

Even a documented/explicit DSL pattern for “API key security in cookie transport” would help a lot if there is already a supported way that we missed.

If useful, I can provide a smaller standalone reproducer, but the root cause looks pretty directly tied to expr.findKey ignoring e.Cookies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions