Skip to content

Carry cookies across redirects via an opt-in CookieStorage (#2671)#2897

Merged
adamw merged 6 commits into
masterfrom
feature/cookie-storage-redirects
May 29, 2026
Merged

Carry cookies across redirects via an opt-in CookieStorage (#2671)#2897
adamw merged 6 commits into
masterfrom
feature/cookie-storage-redirects

Conversation

@adamw
Copy link
Copy Markdown
Member

@adamw adamw commented May 29, 2026

Closes #2671. Supersedes #2672 (which added a failing test demonstrating the problem, but no fix).

Problem

The Cookie header is a sensitive header, so it is stripped when following a redirect. As a result, cookies set via Set-Cookie during a redirect chain were never sent to subsequent requests in that chain (see #2671).

Approach

Implements the design @adamw outlined in #2671: an opt-in cookie jar.

  • New sttp.client4.wrappers.CookieStorage — an immutable cookie jar. set(setBy, cookies) collects Set-Cookie cookies (rejecting ones whose Domain doesn't match the setting host); forUri(uri) returns the cookies to send to a URI. Matching follows a subset of RFC 6265: domain-matching, path-matching and the Secure attribute. Time-based expiry isn't tracked, but Max-Age <= 0 removes a cookie.
  • RequestBuilder.cookieStorage(storage) attaches a storage to a request (via a request attribute).
  • FollowRedirectsBackend (applied to all backends by default), when a storage is attached, sends the matching stored cookies with each request in a redirect chain and threads an updated storage through to the next request.

Default behaviour is unchanged unless a CookieStorage is explicitly attached.

Tests

  • CookieStorageTest — domain/host-only isolation, subdomain matching, cross-domain rejection, path, Secure, overwrite and Max-Age deletion.
  • FollowRedirectsBackendTest — cookies set across a redirect chain reach subsequent requests when a storage is attached, and are not carried when it isn't.

Full core suite passes (631 tests), cross-compiled for Scala 2.12 / 2.13 / 3.

Credit to @Zerkath for the original report and test case in #2671 / #2672.

The Cookie header is sensitive, so it's stripped when following redirects,
and cookies set via Set-Cookie within a redirect chain were lost. This adds an
opt-in CookieStorage cookie jar, attached to a request via .cookieStorage(...).
FollowRedirectsBackend then, for each request in a redirect chain, sends the
stored cookies matching the request and threads an updated storage to the next
request. Matching follows a subset of RFC 6265 (domain, path, Secure); Max-Age<=0
removes a cookie. Default behaviour is unchanged unless a storage is attached.

Implements the approach outlined in #2671; supersedes the test-only PR #2672.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adamw and others added 5 commits May 29, 2026 08:14
response.unsafeCookies pulls in CookieWithMeta.parse, which uses
java.time.DateTimeFormatterBuilder to parse the Expires attribute - a symbol
not available on Scala Native, which broke linking of the (shared) core
FollowRedirectsBackend. Parse the Set-Cookie headers directly, reading only the
attributes used for storage (Domain, Path, Secure, Max-Age) and ignoring
Expires, so cookie handling works on all platforms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CookieWithMeta.toString formats the Expires date via java.time, which isn't
available on Scala Native, and instantiating it in core (shared) code made that
symbol reachable, breaking the Native link. Represent stored cookies as plain
name/value pairs: parse Set-Cookie headers directly and exchange (name, value)
tuples with the request. The feature now works on JVM, JS and Native.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Compute a path-less cookie's default-path from the setting request's directory
  per RFC 6265 5.1.4, instead of always "/" (a path-less cookie set at /admin/x
  is no longer sent to unrelated paths).
- Fix scaladoc links (PartialRequestBuilder.cookieStorage) and reword the
  CookieWithMeta/Scala Native comment to be accurate.
- Extract the send-matching predicate into a named `matches` helper.
- Add tests: redirect signalled by a thrown exception, domain normalization,
  path-segment-boundary matching, and the RFC default-path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CookieWithMeta is available on all platforms; the actual issue is that its
Set-Cookie rendering/parsing reaches java.time date formatting, which Scala
Native's javalib doesn't provide, breaking the Native link when reached.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adamw adamw merged commit 0b87cf8 into master May 29, 2026
27 checks passed
@adamw adamw deleted the feature/cookie-storage-redirects branch May 29, 2026 17:01
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.

FollowRedirects, set-cookie, final response cookie contents & cookies given on each re-direct request

1 participant