Skip to content

Add AuthType.Cookie(name) for endpoint cookie authentication - #4112

Draft
guizmaii wants to merge 11 commits into
zio:mainfrom
guizmaii:auth-type-cookie
Draft

Add AuthType.Cookie(name) for endpoint cookie authentication#4112
guizmaii wants to merge 11 commits into
zio:mainfrom
guizmaii:auth-type-cookie

Conversation

@guizmaii

Copy link
Copy Markdown
Member

AuthType.Custom(HeaderCodec.cookie) already let an endpoint accept auth via a cookie header, but it returned the entire Header.Cookie value (a NonEmptyChunk[Cookie.Request]) and required the user to hand-roll the lookup-by-name plus the encode-side codec. This adds a typed shortcut that does both:

Endpoint(Method.GET / "download" / fileId)
  .auth(AuthType.Bearer | AuthType.Cookie("session"))
  .unauthorizedStatus(Status.Unauthorized)

The handler / context middleware receives the cookie value as a String. If the named cookie is missing, the codec fails to decode and the endpoint responds with unauthorizedStatus -- matching the existing behaviour for Basic/Bearer/Digest when the Authorization header is absent. Hooked into Endpoint's missing-header handler via a small authHeaderNames helper that walks Or / WithStatus / ScopedAuth wrappers, so composed auth types behave consistently too.

`AuthType.Custom(HeaderCodec.cookie)` already let an endpoint accept
auth via a cookie header, but it returned the entire `Header.Cookie`
value (a `NonEmptyChunk[Cookie.Request]`) and required the user to
hand-roll the lookup-by-name plus the encode-side codec. This adds a
typed shortcut that does both:

```scala
Endpoint(Method.GET / "download" / fileId)
  .auth(AuthType.Bearer | AuthType.Cookie("session"))
  .unauthorizedStatus(Status.Unauthorized)
```

The handler / context middleware receives the cookie value as a `String`.
If the named cookie is missing, the codec fails to decode and the
endpoint responds with `unauthorizedStatus` -- matching the existing
behaviour for `Basic`/`Bearer`/`Digest` when the `Authorization` header
is absent. Hooked into `Endpoint`'s missing-header handler via a small
`authHeaderNames` helper that walks `Or` / `WithStatus` / `ScopedAuth`
wrappers, so composed auth types behave consistently too.
@netlify

netlify Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploy Preview for zio-http ready!

Name Link
🔨 Latest commit e7d274b
🔍 Latest deploy log https://app.netlify.com/projects/zio-http/deploys/6a0310fc7ef6640008306c49
😎 Deploy Preview https://deploy-preview-4112--zio-http.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

guizmaii added 10 commits May 3, 2026 14:00
- Make the codec error message end with " auth required" so the
  endpoint returns `unauthorizedStatus` (matching `Basic`/`Bearer`/
  `Digest`) when the `Cookie` header is present but the named cookie
  is absent. Previously this path leaked a generic decode error.
- Handle `AuthType.Cookie` in both `OpenAPIGen` loops so endpoints
  using it emit an `apiKey` security scheme with `in: cookie` and
  the corresponding security requirement, matching what the
  `AuthType.Custom(HeaderCodec.cookie)` workaround produced.
- Add a test exercising the present-but-wrong-name cookie path.
- Keep "authorization" in authHeaderNames(Custom(_)) for backward
  compatibility: the pre-existing missing-header → unauth flow fired on
  any missing Authorization header regardless of auth type, so a Custom
  auth on Authorization would otherwise silently lose its 401 response.

- Sanitize the OpenAPI components-map key for AuthType.Cookie to
  [A-Za-z0-9._-]+ so unusual cookie names can't crash OpenAPI
  generation. The actual cookie name is preserved verbatim in
  SecurityScheme.ApiKey.name.

- Add three tests for the AuthType.Bearer | AuthType.Cookie("session")
  combined path (Bearer wins, Cookie wins, neither → 401 when
  configured).

- Add an OpenAPIGenSpec test asserting the JSON shape produced by
  AuthType.Cookie("session").
"session" looks like a magic keyword; "myAppAuthCookie" makes it obvious
the argument is the caller's choice of cookie name.
Matches the Scaladoc example and makes it obvious throughout the tests
that the argument to AuthType.Cookie is the caller's chosen cookie
name, not a magic keyword.
- Switch return type from List to Set (the result is only ever used
  with .contains; duplicates make no semantic sense).
- Use a worklist + inner @tailrec loop so deep AuthType trees can't
  overflow the stack.
- Accumulate into a local mutable Set inside the function and freeze
  to immutable on return; the mutation is contained and avoids
  allocating an intermediate Set per recursion step.
Makes the data flow visible in the signature instead of capturing the
mutable Set from the enclosing scope.
…base case

Import scala.collection.mutable so the body uses the short `mutable.Set` name.
Drop the wrapper function and convert from mutable to immutable Set inside
the recursion's base case, so the function's return type is the immutable
`Set[String]` the call site needs.
Outer function takes just the AuthType and returns Set[String];
an inner @tailrec loop carries the mutable accumulator and converts
to immutable Set at the base case. Caller stays simple.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant