Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Session cookie is not set in browser

I have frontend client running on custom Next.js server that is fetching data with apollo client. My backend is graphql-yoga with prisma utilizing express-session.

I have problem with picking correct CORS settings for my client and backend so cookie would be properly set in the browser, especially on heroku.

Currently I am sending graphql request from client with apollo-client having option credentials: "include" but also have tried "same-origin" with no better result.

I can see cookie client in response from my backend in Set-Cookie header, and in devTools/application/cookies. But this cookie is transparent to browser and is lost on refresh.

With this said I also tried to implement some afterware to apollo client as apolloLink that would intercept cookie from response.headers but it is empty.

So far now I'm thinking about pursuing those two paths of resolving the issue.

(I'm only implementing CORS because browser prevents fetching data without it.)

CLIENT

ApolloClient config for client-side:

const httpLink = new HttpLink({
  credentials: "include",
  uri: BACKEND_ENDPOINT,
});

Client CORS usage and config:

 app
    .prepare()
    .then(() => {
      const server = express()
        .use(cors(corsOptions))
        .options("*", cors(corsOptions))
        .set("trust proxy", 1);

...here goes rest of implementation

const corsOptions = {
  origin: [process.env.BACKEND_ENDPOINT, process.env.HEROKU_CORS_URL],
  credentials: true,
  allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie", '*'],
  methods: "GET,POST",
  optionsSuccessStatus: 200
};

My atempt to get headers from response in apolloClient(but headers are empty and data is not fetched afterwards):

const middlewareLink = new ApolloLink((operation, forward) => {
   return forward(operation).map(response => {
     const context = operation.getContext();

     const {response: {headers}} = context;
     if (headers) {
       const cookie = response.headers.get("set-cookie");
       if (cookie) {
         //set cookie here
       }
     }
     return response;
   });

 });

BACKEND

CORS implementaion (remeber that is gql-yoga so I need first to expose express from it)

server.express
  .use(cors(corsOptions))
  .options("*", cors())
  .set("trust proxy", 1);

...here goes rest of implementation
const corsOptions = {
  origin: [process.env.CLIENT_URL_DEV, process.env.CLIENT_URL_PROD, process.env.HEROKU_CORS_URL],
  credentials: true,
  allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
  exposedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
  methods: "GET,HEAD,PUT,PATCH,POST,OPTIONS",
  optionsSuccessStatus: 200
};

Session settings, store is connect-redis

server.express
  .use(
    session({
      store: store,
      genid: () => uuidv4(v4options),
      name: process.env.SESSION_NAME,
      secret: process.env.SESSION_SECRET,
      resave: true,
      rolling: true,
      saveUninitialized: false,
      sameSite: false,
      proxy: STAGE,
      unset: "destroy",
      cookie: {
        httpOnly: true,
        path: "/",
        secure: STAGE,
        maxAge: STAGE ? TTL_PROD : TTL_DEV
      }
    })
  )  

I am expecting session cookie to be set on the client after authentication.

Actual result: Cookie is visible only in Set-Cookie response header but is transparent to browser and not persistent nor set (lost on refresh or page change). Funny enough I can still make authenticated requests for data.

like image 431
pawelkrystkiewicz Avatar asked Nov 06 '22 22:11

pawelkrystkiewicz


1 Answers

This may not be a CORS issue, it looks like a third-party cookie problem.

Behaviour could be different across browsers so I recommend testing several ones. Firefox (as of version 77) seems to be less restrictive. In Chrome (as of version 83) there is an indicator on the far right of the URL bar when a third party cookie has been blocked. You can confirm whether third party cookies is the cause of the problem by creating an exception for the current website.

Assuming your current setup is as follows:

frontend.com
backend.herokuapp.com

Using a custom domain for your backend that is a subdomain of your frontend would solve your problem:

frontend.com
api.frontend.com 

The following setup wouldn't work because herokuapp.com is included in the Mozilla Foundation’s Public Suffix List:

frontend.herokuapp.com
backend.herokuapp.com

More details on Heroku.

like image 70
Jelefra Avatar answered Nov 18 '22 13:11

Jelefra