apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.38k stars 2.66k forks source link

Apollo Client does not pass cookies #4190

Closed serendipity1004 closed 3 years ago

serendipity1004 commented 5 years ago

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'
    })
serendipity1004 commented 5 years ago

I am using following dependencies

    "apollo-boost": "^0.1.22",
    "apollo-client": "^2.4.7",
    "apollo-link-context": "^1.0.10",
    "apollo-link-http": "^1.5.7",
ahrbil commented 5 years ago

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

antenando commented 5 years ago

Same here

piesrtasty commented 5 years ago

Same here, anyone have any luck with this?

ahrbil commented 5 years ago

I think you need to verify your server the issue for me was the cors i was passing cors options in the wrong place now it works as expected

igo commented 5 years ago

@serendipity1004 works here. Doesn't it work during SSR or on client side?

antenando commented 5 years ago

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

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

I was using inside of fetchOptions...

RiChrisMurphy commented 5 years ago

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

calendee commented 5 years ago

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

rrakso commented 5 years ago

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

hallya commented 5 years ago

Hi there,

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

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 ?

TheoMer commented 5 years ago

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: ... }

chaunceyau commented 5 years ago

Anyone have any further insight?

rrakso commented 5 years ago

@TheoMer yup ;)

chaunceyau commented 5 years ago

@rrakso Could you provide a snippet of what is working for you?

garmjs commented 5 years ago

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}`)
);
engbrianlee commented 5 years ago

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

MANTENN commented 5 years ago

FYI: I'm using fetch, and axios, thats what's not setting the cookie header for me.

neil-gebbie-smarterley commented 5 years ago

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.

wanzulfikri commented 5 years ago

Oddly enough, what worked for me was to set an AuthLink with setContext from apollo-link-context and call localStorage.getItem("") within it like so:

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem("");

  return {
    headers: {
      ...headers,
    }
  };
});

I'm not sure if this is a good workaround (maybe dangerous?); you'll be bombarded with reference errors as localStorage is not available when NextJS renders in server-side.

Edit: Eh...doesn't work on mobile.

TheoMer commented 5 years ago

This works for me:

  return new ApolloClient({
    uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
    request: operation => {
      operation.setContext({
        fetchOptions: {
          credentials: 'include',
        },
        headers: { cookie: headers && headers.cookie },
      });
    },
    .......
  });
lawwantsin commented 5 years ago

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.

NinjaOnRails commented 5 years ago

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?

helfi92 commented 5 years ago

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

@RiChrisMurphy I'm seeing a similar issue. Were you able to find a fix for that?

neil-gebbie-smarterley commented 5 years ago

@helfi92 Have you tried any of the solutions in this thread?

helfi92 commented 5 years ago

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.

juicycleff commented 5 years ago

I've been trying in Razzle to no avail. Cookies are passed to playground very ok, but on razzle it's not

neil-gebbie-smarterley commented 5 years ago

@juicycleff What does your createApolloClient file look like?

Do you have a withApolloClient HOC?

seawatts commented 5 years ago

I'm still having an issue where the cookies are not getting sent at all, even with all of the above suggestions.

neil-gebbie-smarterley commented 5 years ago

@seawatts are you using Apollo client and Apollo server?

seawatts commented 5 years ago

Yeah I have tried the following

import { ApolloClient } from 'apollo-client';

const client = new ApolloClient({
    link: ApolloLink.from([
      new HttpLink({
        headers,
        fetchOptions: {
          credentials: 'include',
        },
        uri: process.env.REACT_APP_SERVER_URL,
        credentials: 'include',
      }),
    ]),
    cache: new InMemoryCache(),
  });

and

import ApolloBoostClient from 'apollo-boost';

const client = new ApolloClient({
  uri: process.env.REACT_APP_SERVER_URL,
  headers,
  credentials: 'include',
  fetchOptions: {
    credentials: 'include',
  },
});

Then on the server side I'm using graphql-yoga

neil-gebbie-smarterley commented 5 years ago

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.

mdashmiller commented 5 years ago

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.

arvi commented 5 years ago

@mdashmiller thanks. It worked for me as well. :smiley:

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 :heart_eyes:

dkln commented 5 years ago

It seems that mixing headers: {...} and credentials: 'include' overrides the entire header and removes all cookie related content:

export default new ApolloClient({
  link,
  cache: new InMemoryCache(),
  headers: {
    'x-custom-header': 'test'
   },
  credentials: 'include',
});

In this case I would expect the headers to contain the cookies and the x-custom-header header. It now only includes the x-custom-header.

richardallen commented 4 years ago

I've observed the same behavior described by @dkln Cookies are not included when both headers and credentials are used

ronatory commented 4 years ago

For me what worked was just to set the credentials option also in the POST request for the login endpoint. If you're are using axios like this

    axios
    .post(loginEndpoint, loginParams, { withCredentials: true }) // set withCredentials to true is important!
    .then(response => {
      // handle response
    })

or fetch

response = await fetch (loginEndpoint, {
    method: 'POST',
    credentials: 'include', // set credentials to 'include' is important!
    body: JSON.stringify({
        email,
        pw
    })
})
json = await response.json();

Just for completeness this is my ApolloClient setup:

const createApolloClient = () => {
  return new ApolloClient({
    link: new HttpLink({ uri: EXAMPLE_URL, credentials: 'include' }),
    cache: new InMemoryCache(),
  })
}

Hope this helps someone else too

ArtGurianov commented 4 years ago

Guys, do not mess with headers!!! It will overwrite session! Still don't know how to set custom headers and not lose session, anyone thoughts?

chemicalkosek commented 4 years ago

For anyone struggling with cookies being wiped out on refresh, initial load after deploying to production. You have to attach a domain option to your mutation in res.cookie like: { domain: '.yourdomain.com' } Then will the cookies work on refresh if your frontend is at frontend.yourdomain.com and backend at backend.yourdomain.com. It worked on localhost without this additional setting, because it's the same domain for backend and frontend: localhost. I lost a month on this only thinking it was Apollo Client issue.

TheoMer commented 4 years ago

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',
    });
  2. 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',
      });
morious commented 4 years ago

FWIW my issue was that i did not set with credentials to 'true' in my login call (using axios) - which resulted in subsequent Apollo calls not sending the cookie. So not an issue with Apollo.

counterbeing commented 4 years ago

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!

patrickleet commented 4 years ago

omg I spent so long on this.

Here is how my setup looks:

  1. Auth Server - Keycloak - auth.domain.com
  2. SSR Sapper/Svelte App - www.domain.com
  3. graphql api - content.domain.com

For me, credentials: true on the server (localhost:3001), and credentials: 'include' on the client got things working for me on localhost!

... Then I deployed...

Once I deployed with SSL and the works to Kubernetes, cors was working, but cookies were not being shared.

I must have tried 800 combinations before I came across the concept of a domain cookie.

Should that cookie be the FQDN? The Auth server? The API Server?

I use passport on www which is where the user logs in, and the cookie is stored. I assumed this meant I should use that www FQDN (https://www.domain.com). This did not work.

Turns out you can use cookies across subdomains (not domains), however, and this DID finally work. To do so, set the domain value to .domain.com the cookies started getting passed around!

From the www server:

const cookieSettings = {
  maxAge: 24 * 60 * 60 * 1000,
  keys: config.sessionKey,
  secure: !dev
}

if (!dev && config.cookie && config.cookie.domain) {
  cookieSettings.domain = config.cookie.domain // value: ".domain.com"
}
byurhannurula commented 4 years ago

Hey, I have the same issue and I can't resolve it My FR (Vercel) & BE (Heroku) both are on a subdomains

On Firefox Set-Cookie is not coming from response on Chrome/Brave Set-Cookie is coming but after page reload redirects me to the login page.

This example is working on localhost but not after deploying it.

Apollo client

import withApollo from 'next-with-apollo'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'isomorphic-unfetch'

export default withApollo(({ initialState, headers }) => {
  const isBrowser = typeof window !== 'undefined'
  const backendUrl = ...
  return new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: !isBrowser,
    link: createHttpLink({
      uri: `${backendUrl}/graphql`,
      credentials: 'include',
      ...(!isBrowser && { fetch }),
      headers: {
        ...headers,
        cookie: headers?.cookie,
      },
    }),
    cache: new InMemoryCache().restore(initialState || {}),
  })
})

My Next.js _app.js

const MyApp = ({ Component, pageProps, apollo }) => (
  <>
        <ApolloProvider client={apollo}>
          <UserProvider>
            <SEO />
            <Header />
            <main role="main">
              <Component {...pageProps} />
            </main>
          </UserProvider>
        </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)
 "@apollo/react-hooks": "^3.1.5",
 "apollo-cache-inmemory": "^1.6.6",
 "apollo-client": "^2.6.9",
 "apollo-link-http": "^1.5.17",
 "graphql": "^15.0.0",
 "graphql-tag": "^2.10.3",
 "isomorphic-unfetch": "^3.0.0",
 "next": "9.4.0",
 "next-with-apollo": "^5.0.1",
 "react": "16.13.1",
 "react-dom": "16.13.1",

Server code:

app.set('trust proxy', 1)

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

app.use(
  cors({
    origin: process.env.FRONTEND_URL || `http://localhost:3000`,
    credentials: true,
  }),
)

app.use(
  session({
    store,
    name: process.env.SESS_NAME,
    secret: process.env.SESS_SECRET,
    saveUninitialized: false,
    resave: false,
    cookie: {
      secure: !dev,
      httpOnly: true,
      domain: process.env.SESS_DOMAIN,
      maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
    },
  }),
)
chemicalkosek commented 4 years ago

@byurhanbeyzat

My FR (Vercel) & BE (Heroku) both are on a subdomains

You should have frontend and backend on subdomains within the same domain. Like frontend.yourdomain.com and backend.yourdomain.com. Then the cookies will be set.

Also I don't think you should spread headers there. At least it was breaking my code.

byurhannurula commented 4 years ago

@chemicalkosek

My FR (Vercel) & BE (Heroku) both are on a subdomains

You should have frontend and backend on subdomains within the same domain. Like frontend.yourdomain.com and backend.yourdomain.com. Then the cookies will be set.

Also I don't think you should spread headers there. At least it was breaking my code.

Yes, they both are on same custom domain. If I remove headers it's not working event on localhost.

chemicalkosek commented 4 years ago

What is your process.env.SESS_DOMAIN It should be yourdomain.com.

byurhannurula commented 4 years ago

@chemicalkosek SESS_DOMAIN - '.bbn.codes' app - bank.bbn.codes api - bank-api.bbn.codes

chemicalkosek commented 4 years ago

@byurhanbeyzat It looks like the cookie is not being wiped out on refresh, hmmm. Well I don't know :( . Btw you're using next-with-apollo 5.0.0, you should pass getDataFromTree in order to have SSR

// import { getDataFromTree } from '@apollo/react-ssr'; // You can also override the configs for withApollo here, so if you want // this page to have SSR (and to be a lambda) for SEO purposes and remove // the loading state, uncomment the import at the beginning and this: // // export default withApollo(Index, { getDataFromTree });

patrickleet commented 4 years ago

console.log your cors and cookie settings to make sure they actually are what you expect