Append multiple "Set-Cookie" Http Headers in ApolloServer

Greetings.
I am trying to set multiple cookies from the server on the client (to make storing auth tokens more secure by using HttpOnly cookies). I’ve tried the following approach so far, but it only sets one “Set-Cookie” header with multiple values. But this does not work, only the first cookie is actually set on the client (Tested with Firefox).

Here is short code for constructing the ApolloServer object:

import dotenv from "dotenv";
dotenv.config();

import { ApolloServer, AuthenticationError } from "apollo-server";
import jwt from "jsonwebtoken";
import mongoose from "mongoose";
import { IsPublicEndpoint } from "shared/util/helper.js";

mongoose.connect(process.env.MONGODB_URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useFindAndModify: false
});

const db = mongoose.connection;
db.on("error", (error) => Logger.error(error));
db.once("open", () => Logger.info("Connected to Mongoose"));

import typeDefs from "server/typeDefs/index.js";
import resolvers from "server/resolvers/index.js";
import Logger from "shared/util/logger.js";

const server = new ApolloServer({
  context: async ({ req }) => {
    let auth =
      req.headers && req.headers.authorization
        ? req.headers.authorization
        : null;

    if (auth) {
      const jwtToken = auth.split(" ")[1];
      const verifiedUser =
        jwtToken && jwt.verify(jwtToken, process.env.ACCESS_TOKEN_SECRET);
      if (verifiedUser) {
        return {
          user: verifiedUser
        };
      } else {
        throw new AuthenticationError("Your token does not verify!");
      }
    } else {
      if (IsPublicEndpoint(req.body.operationName)) {
        Logger.info(req.body.operationName, " Is a public endpoint");
      } else {
        throw new AuthenticationError("You must be logged in!");
      }
    }
  },
  cors: {
    origin: "http://localhost:8080",
    credentials: true
  },
  typeDefs: typeDefs,
  resolvers: resolvers,
  formatResponse: (response, requestContext) => {
    if (response.data?.login) {
      const tokenExpireDate = new Date();
      tokenExpireDate.setDate(tokenExpireDate.getDate() + response.data.login.expiresIn);
      requestContext.response.http.headers.append("Set-Cookie",
        `accessToken=${response.data.login.accessToken}; expires=${tokenExpireDate}`);
      requestContext.response.http.headers.append("Set-Cookie",
      `accessLevel=${response.data.login.accessLevel}; expires=${tokenExpireDate}`);
    }
    return response;
  },
});

server.listen().then(({ url }) => {
  Logger.info(`Apollo Server ready at ${url}`);
});

I would be eternally grateful if someone can point me to the right direction.

@demi instead of:

formatResponse: (response, requestContext) => {
  if (response.data?.login) {
    const tokenExpireDate = new Date();
    tokenExpireDate.setDate(tokenExpireDate.getDate() + response.data.login.expiresIn);
    requestContext.response.http.headers.append("Set-Cookie",
      `accessToken=${response.data.login.accessToken}; expires=${tokenExpireDate}`);
    requestContext.response.http.headers.append("Set-Cookie",
    `accessLevel=${response.data.login.accessLevel}; expires=${tokenExpireDate}`);
  }
  return response;
},

maybe try:

formatResponse: (response, requestContext) => {
  if (response.data?.login) {
    const tokenExpireDate = new Date();
    tokenExpireDate.setDate(tokenExpireDate.getDate() + response.data.login.expiresIn);
    requestContext.context.res.set(
      'set-cookie', 
      [
        `accessToken=${response.data.login.accessToken}; expires=${tokenExpireDate}`, 
        `accessLevel=${response.data.login.accessLevel}; expires=${tokenExpireDate}`
      ]
    );
  }
  return response;
},
1 Like