apollo-client: Apollo Client does not pass cookies

I am currently using nextJS with apollo and it’s completely unusable for me because the cookie is not passing in every request.

I would be much grateful if someone can just point me to right direction to pass cookies properly.

Even the apollo nextjs example is buggy itself https://github.com/adamsoffer/next-apollo-example

Even in that example, cookies are not being sent in the request.

I am trying every possible way of setting cookies in config without success.

Some people say swapping ApolloClient to ApolloBoost have solved it but neither of the packages work for me.

Below is an example of what I have tried

new ApolloClient({
        connectToDevTools: process.browser,
        ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
        link: new HttpLink({
            uri: APOLLO_ENDPOINT, // Server URL (must be absolute)
            opts:{
                credentials:'include'
            },
            credentials: 'include', // Additional fetch() options like `credentials` or `headers`,
        }),
        cache: new InMemoryCache().restore(initialState || {}),
        fetchOptions:{
            credentials:'include'
        },
        credentials:'include'
    })

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 44
  • Comments: 93 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Figured it out. credentials:'include' should be in the root of the config. Like,

new ApolloClient({
  credentials: 'include',
})

I was using inside of fetchOptions

Put the credentials: "include" in your httpLink, like so.

const httpLink = createHttpLink({ uri,  credentials: 'include',
 });

Then put that in your ApolloClient. Worked finally without a bunch of custom stuff.

I’m pretty sure I’m still having this issue. My rest calls have cookies attached, however my graphql queries don’t.

I have the same issue with apolloboost the cookies not sent with the request

Hello everybody from 2021, and… I’m having the same issue ((

Add cookie to getServerSideProps on a page by page basis

Easy quick method to add cookie is to pass context.

export async function getServerSideProps(context) {
  const user = await client.query({
    query: GET_COOKIE_USER,
    context: {
      headers: {
        cookie: context.req.headers.cookie
      }
    }
  });

  return {
    props: {
      user: user.data.currentUser
    }
 };
}

If client side is not storing cookie on login or whatever

adding credentials property seems to do the trick

const httpLink = new HttpLink({ 
  uri: "http://localhost:3000/graphql",
  credentials: 'include' // allows cookies
});

The docs are here: https://www.apollographql.com/docs/react/networking/authentication/#cookie

i’m using next-with-apollo and it’s working, you can do something like this

// createClient.js
import ApolloClient from "apollo-boost";
import withData from "next-with-apollo";

const createClient = ({ headers }) => {
  return new ApolloClient({
    uri: process.env.ENDPOINT,
    request: operation => {
      operation.setContext({
        fetchOptions: {
          credentials: "include"
        },
        headers
      });
    }
  });
};

export default withData(createClient);
// index.js server
const cookieParser = require("cookie-parser");
require("dotenv").config({ path: "var.env" });
const createServer = require("./createServer");

const server = createServer();

server.express.use(cookieParser());

server.express.use((req, res, next) => {
  const { token } = req.cookies;
  console.log(token);
  next();
});

server.start(
  {
    cors: {
      credentials: true,
      origin: ["http://localhost:3000"] // frontend url.
    }
  },
  r => console.log(`Server is running on http://localhost:${r.port}`)
);

Took us a while to figure this out, but here were our couple of sticking points.

Currently the only place we configured credentials: 'include' was in the HttpLink configuration object. This will fail giving you a cors error if you have not already set them up.

Assuming your setup is similar to ours, you are using the applyMiddleware function to make changes to an instance of an express server. Within this object is where you must pass your cors configuration.

Ours looks like this for example (for dev):

{
  origin: 'http://localhost:3000',
  optionsSuccessStatus: 200,
  credentials: true,
};

All of a sudden our client was passing cookies. Good luck!

Hi, I found that when I want to make request with “Cookie” then apollo client is not sending it, but just when I change it to “Cookies” then everything is ok XD

Hi there,

I’m still having the issue here. I’m working with:

  • Angular 7
  • Apollo-client 2.4.7
  • Chrome with cookie enabled and no extension

withCredentials is sets to true in my apollo.config.ts file:

    const uri = '/graphql';
    const link = httpLink.create({
      uri,
      withCredentials: true
    });

    apollo.create({
      link,
      cache: new InMemoryCache(),
      defaultOptions: {
        watchQuery: {
          errorPolicy: 'all'
        }
      },
      connectToDevTools: true
    });

Is there anybody that can tell us why ?

Heyo! I was running into the issue of apollo-client sending / setting cookies client side fine, but was getting problems with it server side. I use an afterware server-side to set cookies. Here’s my fix with typescript:

import {
  ApolloClient, ApolloLink, HttpLink, InMemoryCache, gql,
} from '@apollo/client';
import { NormalizedCacheObject } from '@apollo/client/cache';
import { GetServerSidePropsContext } from 'next';
import { useMemo } from 'react';

const createClient = (ctx?: GetServerSidePropsContext) => {
  const setCookiesAfterware = new ApolloLink((operation, forward) => forward(operation).map((response) => {
    ctx?.res.setHeader('set-cookie', operation.getContext().response.headers.raw()['set-cookie'] || '');
    return response;
  }));

  return new ApolloClient({
    link: setCookiesAfterware.concat(new HttpLink({ uri: 'http://localhost/graphql', headers: { cookie: ctx.req.headers.cookie } })),
    cache: new InMemoryCache(),
  });
};

let client: ApolloClient<NormalizedCacheObject> | undefined;
const initializeClient = (initialState?: NormalizedCacheObject, ctx?: GetServerSidePropsContext) => {
  const apolloClient = client ?? createClient(ctx);

  if (initialState) {
    const prevState = apolloClient.extract();

    apolloClient.restore({ ...prevState, ...initialState, ...{ ROOT_QUERY: { ...prevState.ROOT_QUERY, ...initialState.ROOT_QUERY } } });
  }

  if (typeof window === 'undefined') return apolloClient;

  client ??= apolloClient;

  return client;
};

const useClient = (initialState?: NormalizedCacheObject) => useMemo(() => initializeClient(initialState), [initialState]);
const getClient = (ctx: GetServerSidePropsContext) => initializeClient(undefined, ctx);

export { gql, useClient };
export default getClient;

I’ve fixed it for myself using the latest libraries next@9.0.3, next-with-apollo@4.2.0 and react-apollo@3.0.0. Cookies are passed with every request, SSR working as expected and no errors. Code is here. I had to remove .restore(initialState || {}) so it’s just cache: new InMemoryCache() and now it’s fully working. Only thing still not working is Safari. Anyone fixed it for Safari?

Figured it out. credentials:'include' should be in the root of the config. Like,

new ApolloClient({
  credentials: 'include',
})

I was using inside of fetchOptions

This also fixed my issue, the docs are a bit confusing as credentials: ‘include’ is nested under fetchOptions; putting in the root fixed my issue. Is there a reason why this option can be put in two places? Only the root worked for me and caused a lot of confusion

Hi, I found that when I want to make request with “Cookie” then apollo client is not sending it, but just when I change it to “Cookies” then everything is ok XD

@rrakso Did you mean like this?

headers: { cookies: … }

I’m having a similar problem, but I actually think it is a browser issue and nothing to do with the Apollo Client.

When I have my front-end hosted on Heroku like frontend.herokuapp.com and my yoga backend on something like backend.herokupapp.com, my Graphql queries will only retain the cookie if my browsers do NOT have “Disable 3rd Party Cookies” set or Safari’s “Prevent Cross-site Tracking” enable.

It seems to me, the browser considers any cookie from different subdomain’s to be 3rd party.

There are varying degrees of this:

Opera - Cookies blocked if “Block Third Party Cookies” enabled Firefox - Cookies work even with Block Third Party Cookies - Trackers" enabled but blocked if “All third party cookies” is selected Chrome - Cookies work unless “Block Third Party Cookies” enabled

@neil-gebbie-smarterley - I have an authToken saved in memory that expires every 10mn that I pass on each and every requests. And a long-lived refreshToken that I must NOT pass on every requests saved in an httpOnly cookie. This refresh token is used every 10mn to get a new authToken and a new refreshToken.

Thanks I’ll take a look at it.

Hi there, Thanks for your precious infos.

We want to store refreshTokens only (not authToken) in an httpOnly cookie but if we use credentials:include, the refresh token will be passed on every request which is far from optimal. Is my use case unique or am I missing something?

Thanks

Thank you everyone. I found solution for myself I’ve decided to use next-apollo-example that I found on the internet with a little tweaks.

If someone needs here is the code

initApollo.js

import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createHttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import fetch from 'isomorphic-unfetch'

let apolloClient = null

// Polyfill fetch() on the server (used by apollo-client)
if (typeof window === 'undefined') {
  global.fetch = fetch
}

function create(initialState, { getToken, fetchOptions }) {
  const backendUrl =
    process.env.NODE_ENV === 'production'
      ? `https://bank-api.bbn.codes`
      : 'http://localhost:2000'

  const httpLink = createHttpLink({
    uri: `${backendUrl}/graphql`,
    credentials: 'include',
    fetchOptions,
  })

  const authLink = setContext((_, { headers }) => {
    const token = getToken()

    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : '',
      },
    }
  })

  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  const isBrowser = typeof window !== 'undefined'

  return new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
    link: authLink.concat(httpLink),
    cache: new InMemoryCache().restore(initialState || {}),
  })
}

export default function initApollo(initialState, options) {
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (typeof window === 'undefined') {
    let fetchOptions = {}

    // If you are using a https_proxy, add fetchOptions with 'https-proxy-agent' agent instance
    // 'https-proxy-agent' is required here because it's a sever-side only module
    if (process.env.https_proxy) {
      fetchOptions = {
        // agent: new (require('https-proxy-agent'))(process.env.https_proxy),
      }
    }

    return create(initialState, {
      ...options,
      fetchOptions,
    })
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = create(initialState, options)
  }

  return apolloClient
}

witApollo.js

import React from 'react'
import cookie from 'cookie'
import Head from 'next/head'
import PropTypes from 'prop-types'
import { getDataFromTree } from '@apollo/react-ssr'

import initApollo from './initApollo'

function parseCookies(req, options = {}) {
  return cookie.parse(req ? req.headers.cookie || '' : document.cookie, options)
}

export default (App) => {
  return class WithData extends React.Component {
    static displayName = `WithData(${App.displayName})`

    static propTypes = {
      apolloState: PropTypes.object.isRequired,
    }

    static async getInitialProps(ctx) {
      const {
        Component,
        router,
        ctx: { req, res },
      } = ctx
      const apollo = initApollo(
        {},
        {
          getToken: () => parseCookies(req).sessionId,
        },
      )
      ctx.ctx.apolloClient = apollo
      let appProps = {}

      if (App.getInitialProps) {
        appProps = await App.getInitialProps(ctx)
      }

      if (res && res.finished) {
        // When redirecting, the response is finished.
        // No point in continuing to render
        return {}
      }

      if (typeof window === 'undefined') {
        // Run all graphql queries in the component tree
        // and extract the resulting data
        try {
          // Run all GraphQL queries
          await getDataFromTree(
            <App
              {...appProps}
              Component={Component}
              router={router}
              apolloClient={apollo}
            />,
          )
        } catch (error) {
          // Prevent Apollo Client GraphQL errors from crashing SSR.
          // Handle them in components via the data.error prop:
          // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
          console.error('Error while running `getDataFromTree`', error)
        }
        // getDataFromTree does not call componentWillUnmount
        // head side effect therefore need to be cleared manually
        Head.rewind()
      }

      // Extract query data from the Apollo's store
      const apolloState = apollo.cache.extract()
      return {
        ...appProps,
        apolloState,
      }
    }

    constructor(props) {
      super(props)
      // `getDataFromTree` renders the component first, the client is passed off as a property.
      // After that rendering is done using Next's normal rendering pipeline
      this.apolloClient = initApollo(props.apolloState, {
        getToken: () => {
          return parseCookies().sessionId
        },
      })
    }

    render() {
      return <App {...this.props} apolloClient={this.apolloClient} />
    }
  }
}

_app.js

const MyApp = ({ Component, pageProps, apolloClient }) => (
  <>
  	<ApolloProvider client={apolloClient}>
        <SEO />
       	<Header />
        	<main role="main">
        	<Component {...pageProps} />
    	</main>
  	</ApolloProvider>
  </>
)

MyApp.getInitialProps = async ({ Component, ctx }) => {
  let pageProps = {}

  const { loggedInUser } = await checkLoggedIn(ctx.apolloClient)

  // Check whether path is an "authorization" specific page
  const auth = isAuthPath(ctx.asPath)

  if (!loggedInUser.me) {
    // User is not logged in. Redirect to Login.
    if (!auth) redirect(ctx, '/login')
  } else if (auth) {
    // User is logged in. Redirect to Dashboard.
    redirect(ctx, '/')
  }

  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx)
  }

  return { pageProps, loggedInUser }
}

export default withApollo(MyApp)

And added this middleware code to the backend

app.use((req, _, next) => {
  const { authorization } = req.headers

  if (authorization) {
    const token = authorization.split(' ')[1]
    req.headers.cookie = `${process.env.SESS_NAME}=${token}`
  }

  return next()
})

@mdashmiller thanks. It worked for me as well. 😃

const httpLink = createUploadLink({
  uri: util.graphQLUrl(),
  credentials: 'include',
});
export default new ApolloClient({
  link,
  cache: new InMemoryCache(),
  credentials: 'include',
});

After doing this, the session created on back-end via express-session now appears in Application > Cookies 😍

I had this issue using apollo client and apollo-server-express

With my use-case I needed to put credentials: ‘include’ in two places:

First, the cookie was not being created. Adding credentials: ‘include’ to createHTTPLink() solved this. Then I was getting connection refused errors when I would query the server. Placing credentials: ‘include’ in new ApolloClient() solved this issue.

I think I understand what’s happening. The client and server I’m working with are both subdomains of herokuapp (e.g…, ui.herokuapp.com and web-server.herokuapp.com). From https://devcenter.heroku.com/articles/cookies-and-herokuapp-com:

applications in the herokuapp.com domain are prevented from setting cookies for *.herokuapp.com

Using a custom domain will probably resolve the issue.

I had to do this for our build a few days ago, here is how I did it - https://gist.github.com/neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac

Our set it is: Next/Apollo Client/Apollo Server/REST data source

It’s basically just passing it through to the server, I’m not sure if the document.cookie is needed for subsequent requests, but it seems to be working ok for us, not in production yet, but it’s passing cookies.

@dclipca FYI I think that because you’re using a custom link, the second credentials: "include" (the one you’re passing directly to ApplloClient) is not needed.

Add cookie to getServerSideProps on a page by page basis

Easy quick method to add cookie is to pass context.

@gregg-cbs I believe this is what we want to avoid (although this is what I do now). Repetitive code in many pages is a bad practice. Need to find a solution for this.

@allanesquina @apollo/client@3.0.2 is a fairly old version of AC3; can you try with @apollo/client@latest? If that doesn’t work, can you provide a small runnable reproduction that clearly demonstrates the issue you’re seeing? The link you provided is for one of Next’s demo apps. To be able to properly troubleshoot this, we’ll need a focused reproduction that shows the problem. Thanks!

I’m using this example, you may use that to reproduce as it has the same issue.

https://github.com/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client-auth

Try to use getServerSideProps to get data from a protected resolver.

I hope it helps.

@alex-r89 I think you should also apply the same cors options to .applyMiddleware. You can try also cors settings in app.listen. At least that’s how it’s working for me

const corsOptions = {
  origin: process.env.FRONTEND_URL,
  credentials: true,
};

server.applyMiddleware({ app, cors: corsOptions, path: '/' });
app.listen(
  {
    port: process.env.PORT || 4000,
    cors: corsOptions,
    path: '/',
  },
  () => console.log(`Server is running at ${server.graphqlPath}`)
);

You should also try to set the domain value in the cookie object because it might be set with your server domain api.mydomain.xyz and not .mydomain.xyz (check in devtools with what domain the cookie is created, I’m not sure right now)

As both @lawwantsin and @chemicalkosek stated, to ensure that my cookie is boh not wiped out and detected on refresh, I did the following:

  1. ApolloClient
  const httpLink = createHttpLink({
    uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
    credentials: 'include',
  });
  1. Back end
      const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
      ctx.res.cookie('token', token, {
        domain: process.env.NODE_ENV === 'development' ? process.env.LOCAL_DOMAIN : process.env.APP_DOMAIN,
        secure: process.env.NODE_ENV === 'development' ? false : true,
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365, // 1 year cookie
        sameSite: 'lax',
      });

I think you need to explicitly pass the cookies through getInitialProps in your withApolloClient HOC. Check out the gist(https://gist.github.com/neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac) I posted - it might look like a lot of work to get it going, but if you log the cookies out at every step of the process you can see them being passed along from the browser through Apollo, and then onto it’s final destination.

We have this set up working in production.