22

I'm using API Gateway's Proxy integration to call a Lambda. The output format specification is this follow JSON format:

{
  "statusCode": httpStatusCode,
  "headers": { "headerName": "headerValue", ... },
  "body": "..."
}

In one response I wish to set two cookies (two different auth cookies) but JSON doesn't allow having two identical keys in the headers object (OK, technically the spec does but most libraries do not).

RFC 7230 notes that Set-Cookie should be handled specially but I can't see how I can send multiple Set-Cookie values through API gateway.

Does anyone know whether this is possible?

Community
  • 1
  • 1
sihil
  • 2,161
  • 12
  • 22

5 Answers5

22

As of November 2018 this is possible using the multiValueHeaders field in the response instead of headers (see announcement).

As an example instead of:

{
  "statusCode": 200,
  "body": "testing multiple set-cookie headers",
  "headers": {
    "X-Test-Header": "baking experiment",
    "Set-Cookie": "cookie1=chocolate-chip",
    "Set-Cookie": "cookie2=oatmeal",
    "Content-Type": "text/plain"
  }
}

You can respond with:

{
  "statusCode": 200,
  "body": "testing multiple set-cookie headers",
  "multiValueHeaders": {
    "X-Test-Header": ["baking experiment"],
    "Set-Cookie": ["cookie1=chocolate-chip", "cookie2=oatmeal"],
    "Content-Type": ["text/plain"]
  }
}

Note that you can use a mix of headers and multiValueHeaders:

{
  "statusCode": 200,
  "body": "testing multiple set-cookie headers",
  "headers": {
    "X-Test-Header": "baking experiment",
    "Content-Type": "text/plain"
  },
  "multiValueHeaders": {
    "Set-Cookie": ["cookie1=chocolate-chip", "cookie2=oatmeal"]
  }
}

However using the same header in both will mean that the value under headers is dropped.

See the documentation for more details.

When using only the header field (as available prior to Nov 2018) I tried sending the following manually curated JSON as a response:

{
  "statusCode": 200,
  "body": "testing multiple set-cookie headers",
  "headers": {
    "X-Test-Header": "baking experiment",
    "Set-Cookie": "cookie1=chocolate-chip",
    "Set-Cookie": "cookie2=oatmeal",
    "Content-Type": "text/plain"
  }
}

The cookies that API gateway returns in response to a CURL request are:

< Content-Type: text/plain
< Content-Length: 35
< Connection: keep-alive
< Date: Thu, 29 Sep 2016 11:22:09 GMT
< Set-Cookie: cookie2=oatmeal
< X-Test-Header: baking experiment
< X-Cache: Miss from cloudfront

As you can see the first Set-Cookie is dropped on the floor.

sihil
  • 2,161
  • 12
  • 22
  • @MarkB that is not true, [see rfc6265](https://tools.ietf.org/html/rfc6265#page-7). You need to be able to respond with multiple `Set-Cookie` headers because each cookie could have different levels of cookie security, expirations, etc. – idbehold Sep 29 '16 at 17:37
  • I think ideally AWS would instead utilize the [the `Headers` interface of the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Headers). A `Headers` instance allows you to `headers.append(key, value)` meaning that you can set the `Set-Cookie` header multiple times. Then you could invoke the Lambda callback with the `headers` property equal to either a plain object *or* a `Headers` instance. – idbehold Sep 29 '16 at 17:53
5

As answered, to date, API Gateway will drop identical keys, only setting one of the cookies.

However, a workaround exists. You can change the casing of the string 'Set-Cookie' so the keys are not unique. For example, you could use the keys set-cookie, Set-cookie, sEt-cookie, and the headers will be preserved and 3 different cookies would be set.

Because the RFC standard makes headers case-insensitive this should work with all RFC-compliant clients.

So, you could rewrite your set-cookie headers, permuting all the possible casings of "Set-Cookie" to get around this.

This technique (hack) is employed by Zappa, a popular serverless framework written in Python.

sytech
  • 16,854
  • 2
  • 29
  • 61
  • 2
    Only just seen this and obviously now fixed properly, but whoever thought this hack up deserves a unicorn. Brilliant. Thanks for bringing it to my attention @sytech – sihil Sep 29 '20 at 12:53
3

As Mark B pointed out, you can/should achieve this by setting multiple cookie name/value pairs in a single Set-Cookie header. The browser should interpret this correctly.

Cookie: a=1; b=2

Edit: as pointed out by OP, there are use cases that require multiple instances of the header. We've added it our backlog along with supporting multiple header names on incoming requests.

jackko
  • 6,558
  • 25
  • 37
  • 2
    [See rfc6265](https://tools.ietf.org/html/rfc6265#page-7). You need to be able to respond with multiple `Set-Cookie` headers because each cookie could have different levels of cookie security, expirations, etc. – idbehold Sep 29 '16 at 17:47
  • I see, good point. I'll have to add this to our backlog; it may take a while if we have to change the shape coming back from Lambda and accept both formats. – jackko Sep 29 '16 at 18:01
  • The suggested `Set-Cookie` isn't a valid use of the header. You may only set one cookie per header (unlike `Cookie` sent from the browser, which works exactly as you say above). Everything after the first `;` is interpreted as options such as expiry, max-age etc. See the syntax in [the RFC](https://tools.ietf.org/html/rfc6265#section-4.1.1). – sihil Sep 29 '16 at 21:00
  • I've just tried this with Chrome. When Chrome receives a headers of `Set-Cookie": "cookie1=chocolate-chip; cookie2=oatmeal`, Chrome sets `cookie1=chocolate-chip` and discards the invalid attribute - which follows the RFC as far as I can tell. Your update won't work at all - `Cookie` headers are sent form the browser to the server rather than from the server to the browser (sending all accumulated cookies back to the server on each method call). – sihil Sep 30 '16 at 09:34
  • @JackKohn-AWS, wouldn't it be as simple as allowing either a string (which is the current syntax) or an array of strings. Sounds backward compatible to me. – Arlen Beiler Aug 29 '17 at 02:21
  • 3
    hi, what is the status of this now? – supersan Nov 04 '17 at 10:44
3

Use multiValueHeaders:

response.multiValueHeaders = {
  "Set-Cookie": [
    'cookie1=value1',
    'cookie1=value1'
  ]
}

or:

{
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2",...], ... },
    "body": "..."
}

https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format

Hugodby
  • 1,004
  • 9
  • 23
0

Couple of years late, but I just required to implement something like this and this is how I was able to make it work:

...

//15 minutes
var expirationTime = new Date(new Date().getTime() + 15 * 60 * 1000);
//30 minutes
var expirationTime2 = new Date(new Date().getTime() + 30 * 60 * 1000);

var response = {};

var cookies = [];
cookies.push("testCookie={'keyX':'valx', 'keyy':'valy'}; Expires=" + expirationTime + ";");
cookies.push("testCookie2={'key1':'val1', 'key2':'val2'}; Expires=" + expirationTime2 + ";");

response.headers["Set-Cookie"] =  cookies;

...

Each array item will be processed independently, so you can add as many cookies to the array with different settings.

i.e.

cookies.push("testCookie3={'key1':'val1', 'key2':'val2'}; Expires=" + expirationTime2 + "; Max-Age=...");

cookies.push("testCookie4={'key1':'val1', 'key2':'val2'}; Expires=" + expirationTime2 + "; Domain=<domain-value>; Path=<path-value>");
Rolo
  • 2,918
  • 1
  • 22
  • 23