multiple set-cookie header using expires date with comma

Hello there,

I’ve also came across the issue about multiple set-cookie on response. This is a problem, particularly when the expires attribute uses the date format set by netscape (Sat, 22 Mar 2025 14:46:00 GMT).

It was when we did direct testing with Expires attribute having the comma vs a random date or different format that we went nuts.

So below example generates only a single Set-Cookie on postman

context.setVariable('response.header.Set-Cookie.1', 'xxxx_it=eyJ0A; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.2', '28e67=; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.3', 'xxxx_at=U5OhG4zHZM; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); 

where below generates three

context.setVariable('response.header.Set-Cookie.1', 'xxxx_it=eyJ0A; Domain=.xpto.com; Path=/; Expires=22 Mar 2025 14:46:00 GMT; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.2', '28e67=; Domain=.xpto.com; Path=/; Expires=22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.3', 'xxxx_at=U5OhG4zHZM; Domain=.xpto.com; Path=/; Expires=22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); 

We already looked into few threads:

And have opted for the Max-Age solution. Just waiting to see if that impacts anything on the API client (i.e. website).

I understand that from a community point of view Max-Age should totally replace Expires, however wanted to understand if there is any internal ticket raised for fixing.

Just in case we have someone asking us to use Expires instead.

We also be raising this within our Apigee support.

Thank you.

I don’t know of any internal ticket that tracks “fixing” this behavior.

I am not sure it is broken. I read your post and I don’t quite understand what you are observing, what you are expecting to observe, and how they are different. But I will make some comments.

If you understood the other posts on this community site you realize that the HTTP spec treats comma-separated values in a single request or response header as distinct. So a header line (in a request or response message) like this:

header-name: A, B, C 

…is equivalent to three distinct header lines like this:

header-name: A header-name: B header-name: C 

…and apps (clients or servers that use HTTP) are permitted to use either form, to communicate the same meaning. Further, proxy servers are allowed to collapse or expand headers, or convert from one form to the other, without changing the meaning of the message. Apps on either end who receive consolidated headers or expanded (multi) headers must be able to handle them and must treat them as equivalent, per the spec.

The same is true even if A, B, and C are replaced with complicated, lengthy strings, with semicolons and equals signs, as you would see in a Set-Cookie. One Set-Cookie , with multiple comma-separated values, must be treated as equivalent to three Set-Cookie headers, each with distinct values.

There is a related issue, with the “Expires” part of a cookie. If a system uses a date formatted as per IETF RFC 2822 (as an example, something like “Sat, 22 Mar 2025 14:46:00 GMT”) inside an HTTP header, that conflicts with the HTTP spec in this way: A date so formatted is intended to be ONE value, but because of the presence of the comma, when inserted into an HTTP header, the HTTP spec says it is really two values, and can be represented, per http rules, in two header lines. This inconsistency is not a total disaster, because most systems are often designed to tolerate this situation, specifically around things that are expected to be dates. Typically systems can parse headers that contain dates formatted this way, and not fail. Even so it’s probably better and cleaner for systems producing the headers to use a Max-Age with a single numeric value, rather than an Expires with an RFC 2822-formatted date, wherever possible. The Max-Age has no commas and so does not exhibit the inconsistency .

Aside from the Expires thing, I would say if your client systems cannot tolerate three distinct Set-Cookie headers, they’re broken, and they should be fixed or updated. I highly doubt that the engineers on the Apigee side will consider changing Apigee to accommodate clients that are misbehaving in this way. That’s why I said in the beginning, I don’t know of any internal ticket that tracks “fixing” this behavior. As far as I can see there is nothing to “fix” in Apigee.

I empathize with you - sometimes older clients do not behave well. It’s possible a client app might insist on getting three Set-Cookie headers, or might insist on getting only one. Per HTTP, that is poor behavior, but it happens. While I don’t make the decisions, I don’t think the engineers at Google will agree to modify Apigee to accommodate that kind of misbehavior in external systems.

Hi @dchiesa1 ,

sorry for not been clear earlier.

Basically, our scenario is this:

  • on all our authenticated APIs we have a pre request shared flow that depending on scenario can refresh the tokens with new values, which need to be set to the browser once the backend API is called. The backend API is legacy and cannot handle these tokens. The refresh action is performed via a service callout policy.

  • authentication depends on 3rd party SaaS solution and was currently setting the cookies using Expires attribute (there are at least 4 cookies)

  • once the backend has been invoked we have a post response shared flow that checks if authentication tokens have been refreshed and if so then we set all the Set-Cookie headers on the main API response.

The issue we found first was that on the pre request shared flow when we were trying to store the new set-cookies into a variable, it was storing (erroneously) double the number of cookies because of the comma separator on Expires. We have applied a workaround by storing the raw value (servicecalloutcontext.header.set-cookie.values.string)

Now the second issue is that on the final policy that injects the Set-Cookie headers when we try to accommodate the set-cookie separately for e.g.

context.setVariable('response.header.Set-Cookie.1', 'xxxx_it=eyJ0A; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.2', '28e67=; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.3', 'xxxx_at=U5OhG4zHZM; Domain=.xpto.com; Path=/; Expires=Sat, 22 Mar 2025 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); 

is that it returns a single folded Set-Cookie response header on Postman - this makes the browser set only the first cookie and ignores the other cookies.

We did some hard coding to understand what Apigee was doing and found out the same is again related with the comma (,) on the Expires attribute, given that if we hard code following example (no commas on the expire attribute):

context.setVariable('response.header.Set-Cookie.1', 'xxxx_it=eyJ0A; Domain=.xpto.com; Path=/; **Expires=22 Mar 2025 14**:46:00 GMT; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.2', '28e67=; Domain=.xpto.com; Path=/; **Expires=22 Mar 2025 14:46:00** GMT; HttpOnly; Secure; SameSite=Strict'); context.setVariable('response.header.Set-Cookie.3', 'xxxx_at=U5OhG4zHZM; Domain=.xpto.com; Path=/; **Expires=22 Mar 2025** 14:46:00 GMT; HttpOnly; Secure; SameSite=Strict'); 

it will generate three Set-Cookie response headers on Postman.

The quick fix for this was to replace Expires attribute by Max-Age attribute. As I followed other threads on Apigee community and read about the latest RFCs, the expires should be replaced by Max-Age.
My concern was if the project were actually asking to maintain the expires attribute, however we got confirmation this morning they will be replacing the cookie generation to use Max-Age instead, so we are all good on this.

However the concern was if we needed to maintain the Expires attribute (given Max-Age is not supported on very old browsers like IE, etc..).

Also the other point you brought was about set-cookie folding or combining multiple set-cookie headers into one. We found some discussion on stackoverflow that points to https://www.rfc-editor.org/rfc/rfc6265 section 3 that states that…

  Origin servers SHOULD NOT fold multiple Set-Cookie header fields into    a single header field.  The usual mechanism for folding HTTP headers    fields (i.e., as defined in [[RFC2616](https://www.rfc-editor.org/rfc/rfc2616)]) might change the semantics of    the Set-Cookie header field because the %x2C (",") character is used    by Set-Cookie in a way that conflicts with such folding. 

Errata version:

   Origin servers SHOULD NOT combine multiple Set-Cookie header fields into     a single header field.  The usual mechanism for combining HTTP headers     fields (i.e., as defined in [RFC2616]) might change the semantics of     the Set-Cookie header field because the %x2C (",") character is used     by Set-Cookie in a way that conflicts with such actions. 

So in some way I see a point to fix this in Apigee code however given we have a workaround this is no longer a priority for us.

Also I agree this is the problem of maintaining compatibility support with very old versions, specs, etc.. We should all look into the “blue sky scenario” and discard all previous assumption. But you also understand where we are coming from.

Thanks.