Hi, I've been trying to send httpOnly cookie to the Apollo Server using Apollo Client / NextJS.
The GraphQL Playground is properly setting the httpOnly Cookie when I test queries.
Intended outcome:
Apollo Server gets the cookie & get the auth user.
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import fetch from 'isomorphic-unfetch';
import getConfig from 'next/config';
import { createHttpLink } from 'apollo-link-http';
const { publicRuntimeConfig: config } = getConfig();
let apolloClient: any = null;
if (!process.browser) {
global.fetch = fetch;
}
function create(initialState: any) {
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser,
link: createHttpLink({
credentials: 'include',
uri: config.API_ENDPOINT
}),
cache: new InMemoryCache().restore(initialState || {})
});
}
export default function initApollo(initialState?: any) {
if (!process.browser) {
return create(initialState);
}
if (!apolloClient) {
apolloClient = create(initialState);
}
return apolloClient;
}
Actual outcome:
Well the cookie is never inside the req object of context.
Sometimes it works but I really don't know why, like when I save a file and the HMR is starting...
How to reproduce the issue:
const IN_PROD = process.env.NODE_ENV === 'production';
const { APOLLO_PORT, JWT_SECRET } = process.env as any;
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { makeSchema } from 'nexus';
import cookieParser from 'cookie-parser';
import jwt from 'jsonwebtoken';
import * as types from './types';
import { default as dataSources } from './datasources';
const app = express();
app.use(cookieParser());
const server = new ApolloServer({
dataSources: () => {
return dataSources;
},
schema: makeSchema({
types,
outputs: false
}),
playground: !IN_PROD,
context: async ({ req, res }) => {
const { token } = req.cookies;
const user = await (token ? jwt.verify(token, JWT_SECRET) : null);
return {
res,
currentUser: user,
};
}
});
server.applyMiddleware({
app,
path: '/',
cors: {
origin: [
...
],
credentials: true
}
});
app.listen({ port: APOLLO_PORT }, () =>
console.log(
`馃殌 Server ready at http://localhost:${APOLLO_PORT}${server.graphqlPath}`
)
);
Versions
System:
OS: macOS 10.14.5
Binaries:
Node: 10.15.0 - ~/.nvm/versions/node/v10.15.0/bin/node
Yarn: 1.15.2 - /usr/local/bin/yarn
npm: 6.4.1 - ~/.nvm/versions/node/v10.15.0/bin/npm
Browsers:
Chrome: 75.0.3770.142
Firefox: 67.0.4
Safari: 12.1.1
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link": "^1.2.12",
"apollo-link-context": "^1.0.18",
"apollo-link-error": "^1.1.11",
"apollo-link-http": "^1.5.15",
Well, for anyone having this kind of problem, I ended up using next-with-apollo for the frontend.
// _app.tsx
import * as React from 'react';
import App, { Container } from 'next/app';
import { ThemeProvider } from 'emotion-theming';
import { withApollo, loadFonts } from '@lib';
import { Filters, theme } from '@ui';
import { ApolloProvider } from '@apollo/react-hooks';
class MyApp extends App {
static async getInitialProps({ Component, ctx }: any) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
componentDidMount() {
loadFonts();
}
render() {
const { Component, pageProps, apollo } = this.props as any;
return (
<Container>
<ThemeProvider theme={theme}>
<ApolloProvider client={apollo}>
<Component {...pageProps} />
</ApolloProvider>
</ThemeProvider>
<Filters />
</Container>
);
}
}
export default withApollo(MyApp);
// withApollo.ts
import withApollo from 'next-with-apollo';
import { ApolloClient } from 'apollo-client';
import fetch from 'isomorphic-unfetch';
import getConfig from 'next/config';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const { publicRuntimeConfig: config } = getConfig();
export default withApollo(({ initialState, headers }: any) => {
const isBrowser = typeof window !== 'undefined';
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser,
link: createHttpLink({
uri: config.API_ENDPOINT,
credentials: 'include',
...(!isBrowser && { fetch }),
headers
}),
cache: new InMemoryCache().restore(initialState || {})
});
});
...
"@apollo/react-hooks": "^0.1.0-beta.11",
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link-http": "^1.5.15",
"next": "^9.0.2",
"next-with-apollo": "^4.0.0",
...
I'm using apollo-boost with next-with-apollo and httpOnly works perfectly locally but not in production. Anyone else seen the same?
Most helpful comment
Well, for anyone having this kind of problem, I ended up using next-with-apollo for the frontend.