Skip to content

aws-lambda preset should use cookies vs set-cookie header in response #245

@brtinney

Description

@brtinney

Environment

aws-lambda deployment setting

nitro via nuxt@3.0.0-rc3

Reproduction

See below

Describe the bug

It's not possible to pass multiple cookies with the set-cookie header via AWS Lambda, so the normalizeOutgoingHeaders does not help with cookies in responses.

const handler = async function handler2(event, context) {
  const url = withQuery(event.path || event.rawPath, event.queryStringParameters || {});
  const method = event.httpMethod || event.requestContext?.http?.method || "get";
  if ("cookies" in event && event.cookies) {
    event.headers.cookie = event.cookies.join(",");
  }
  const r = await nitroApp.localCall({
    event,
    url,
    context,
    headers: normalizeIncomingHeaders(event.headers),
    method,
    query: event.queryStringParameters,
    body: event.body
  });
  return {
    statusCode: r.status,
    headers: normalizeOutgoingHeaders(r.headers),
    body: r.body.toString()
  };
};
function normalizeIncomingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
}
function normalizeOutgoingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : v]));
}

This results in issues like the following for some requests (e.g. adapted @auth0/nextjs code for SSR-compatible serverless auth), as only the first cookie is actually set:

{"url":"/api/auth/callback?code=xxx&state=yyy","statusCode":400,"statusMessage":"checks.state argument is missing","message":"checks.state argument is missing","description":""}

Additional context

Documentation here: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2

I was able to fix the issue with this hack to the handler, which is very inelegant:

const handler = async function handler2(event, context) {
  const url = withQuery(event.path || event.rawPath, event.queryStringParameters || {});
  const method = event.httpMethod || event.requestContext?.http?.method || "get";
  if ("cookies" in event && event.cookies) {
    event.headers.cookie = event.cookies.join("; ");
  }
  const r = await nitroApp.localCall({
    event,
    url,
    context,
    headers: normalizeIncomingHeaders(event.headers),
    method,
    query: event.queryStringParameters,
    body: event.body
  });
  const headers = normalizeOutgoingHeaders(r.headers);
  return Object.assign({
    statusCode: r.status,
    body: r.body.toString()
  }, headers);
};
function normalizeIncomingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
}
function normalizeOutgoingHeaders(headers) {
  const obj = {
    headers: {},
    cookies: []
  };
  for (const k in headers) {
    const v = headers[k];
    if (k.toLowerCase() === 'set-cookie') {
      if (Array.isArray(v)) {
        obj.cookies = obj.cookies.concat(v);
      } else {
        obj.cookies = obj.cookies.concat(v.split(','));
      }
    } else {
      obj.headers[k] = v;
    }
  }
  return obj;
}

Note that I also changed the .join() to use ; because the cookie packages expects that format.

Apologies if this should be on nuxt/framework instead. This took me many hours to find out, and I just wanted to make sure it was documented somewhere to eventually get fixed, so that my hack is no longer required as a post-build step.

Logs

No response

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions