Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next js and Apollo - Cookie not being passed

I am using Next JS with Apollo and have set it up using the following config in my with-data HOC:

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { withClientState } from 'apollo-link-state';
import { getMainDefinition } from 'apollo-utilities';
import { ApolloLink, Observable, split  } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import withApollo from 'next-with-apollo';
import { SubscriptionClient } from 'subscriptions-transport-ws';

import { endpoint, prodEndpoint, WSendpoint, WSprodEndpoint } from '../config';

import defaults from '../apollo-state/graphql/default-state';
import Mutation from '../apollo-state/resolvers/mutation-resolvers';

const wsClient = process.browser ? new SubscriptionClient(process.env.NODE_ENV === 'development' ? WSendpoint : WSprodEndpoint, {
  reconnect: true,
}) : null;


function createClient({ headers }) {
  const wsLink = process.browser ? new WebSocketLink(wsClient) : null;
  const httpLink = new HttpLink({
    uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
    credentials: 'include',
  })

  const link = process.browser ? split(
    // split based on operation type
    ({ query }) => {
      const { kind, operation } = getMainDefinition(query);
      return kind === 'OperationDefinition' && operation === 'subscription';
    },
    wsLink,
    httpLink,
  ) : httpLink;

  const cache = new InMemoryCache();

  const request = async operation => {
    const contextObj = {
      fetchOptions: {
        credentials: 'include'
      },
      headers
    };
    operation.setContext(contextObj);
  }; 

  const requestLink = new ApolloLink((operation, forward) =>
    new Observable(observer => {
      let handle;
      Promise.resolve(operation)
        .then(oper => request(oper))
        .then(() => {
          handle = forward(operation).subscribe({
            next: observer.next.bind(observer),
            error: observer.error.bind(observer),
            complete: observer.complete.bind(observer),
          });
        })
        .catch(observer.error.bind(observer));

      return () => {
        if (handle) handle.unsubscribe();
      };
    })
  );
  // end custom config functions
  const apolloClient = new ApolloClient({
      credentials: 'include',
      ssrMode: !process.browser,
      link: ApolloLink.from([
        onError(({ graphQLErrors, networkError }) => {
          if (graphQLErrors) {
            console.log(graphQLErrors)
          }
          if (networkError) {
            console.log(networkError)
          }
        }),
        requestLink,
        withClientState({
          defaults, // default state
          resolvers: {
            Mutation // mutations
          },
          cache
        }),
        link
      ]),
      cache
  }); // end new apollo client
  return apolloClient;
}

export { wsClient };
export default withApollo(createClient);

Locally everything works fine. I can log in, it automatically logs me in when I visit the site and SSR works no problem. However when I deploy to either Next or Heroku the SSR does not work.

I've looked into the issue and it seems as though it's a common problem with cookies not being sent with requests:

https://github.com/apollographql/apollo-client/issues/4455

https://github.com/apollographql/apollo-client/issues/4190

https://github.com/apollographql/apollo-client/issues/4193

The issue appears to lye with this part of the Apollo config where the headers are sometimes not defined therefore the cookie is not being sent:

  const request = async operation => {
    const contextObj = {
      fetchOptions: {
        credentials: 'include'
      },
      headers
    };
    operation.setContext(contextObj);
  }; 

Some of the workarounds people have mentioned are to manually set the cookie header if headers exists:

  const request = async operation => {
    const contextObj = {
      fetchOptions: {
        credentials: 'include'
      },
      headers: {
        cookie: headers && headers.cookie
      }
    };
    operation.setContext(contextObj);
  }; 

The above amend to the code fixes the server side rendering however when I visit the site with a logged in cookie in my browser it will then no longer log me in automatically (it logs me in automatically with my initial method but wont do SSR on production)

People have mentioned it can be to do with Now or Heroku using a subdomain in the generated URLs you get once you deploy the app and to use a custom domain to resolve the issue. I tried using a custom domain however I am still experiencing the issue. My domain setup is like so:

Frontend domain: mysite.com backend domain: api.mysite.com

Has anyone here experienced the issue and been able to resolve it?

Please let me know if you spot something wrong with my config or how I have setup my domains.

like image 348
red house 87 Avatar asked May 22 '19 19:05

red house 87


1 Answers

Late but try this

you should add cookies option as below. Please make sure you have a cookiein your browser i.e. csrftoken. It should be available. Hope it works.


  const request = async operation => {
    const contextObj = {
      fetchOptions: {
        credentials: 'include'
      },
      //################ CHANGE HERE ##########
      headers: {
        ...header
      }
      cookies: {
        ...cookies
      }
      //######################################

    };
    operation.setContext(contextObj);
  };

like image 161
Ngatia Frankline Avatar answered Oct 01 '22 20:10

Ngatia Frankline