How to set cookies using apollo server express - node.js

I am using apollo server express on my backend and next js on frontend.
I have been trying to set cookies in backend using (response) from context but unfortunately it is not working. i would like your help.
here is my backend code.
import http from 'http';
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
import cors from 'cors';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import { execute, subscribe } from 'graphql';
import {
SubscriptionServer,
ConnectionParams,
ConnectionContext,
} from 'subscriptions-transport-ws';
import { connect } from './Config';
import { typeDefs } from './TypeDefs';
import { resolvers } from './Resolvers';
import { isAuthForSubscription } from './Helpers';
import cookieParser from 'cookie-parser';
const startServer = async () => {
const app = express();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use(cookieParser());
const httpServer = http.createServer(app);
const subscriptionServer = SubscriptionServer.create(
{
schema: makeExecutableSchema({ typeDefs, resolvers }),
execute,
subscribe,
onConnect: async (
connectionParams: ConnectionParams,
_websocket: any,
context: ConnectionContext
) => {
let user = null;
if (connectionParams.Authorization) {
user = await isAuthForSubscription(connectionParams.Authorization);
}
return { context, user };
},
},
{
server: httpServer,
path: '/graphql',
}
);
const server = new ApolloServer({
schema: makeExecutableSchema({ typeDefs, resolvers }),
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
serverWillStart: async () => {
return {
drainServer: async () => {
subscriptionServer.close();
},
};
},
},
],
context: ({ req, res }) => ({ req, res }),
});
await server.start();
server.applyMiddleware({ app });
await new Promise<void>(resolve =>
httpServer.listen({ port: 4000 }, resolve)
);
await connect();
console.log(
`server started on port http://localhost:4000${server.graphqlPath}`
);
return { server, app };
};
startServer();
// and my resolver is this one
import { ApolloError } from 'apollo-server-express';
import {
TCreateAccountArgs,
TLoginArgs,
TContext,
CookieName,
} from '../../__generated__';
import { generate } from '../../Helpers';
import { userModel } from '../../Models';
import { CookieOptions } from 'express';
export const login = async(_: any, args: TLoginArgs, { res, req }: TContext) => {
try {
const find = await userModel.findOne({ username: args.username });
if (!find) {
return new ApolloError('Account not found');
}
const comparePassword = generate.comparePassword(
find.password,
args.password
);
if (!comparePassword) {
return new ApolloError('Incorrect password or username');
}
const generateToken = generate.generateToken({ _id: find._id });
const cookieOptions: CookieOptions = {
httpOnly: true,
maxAge: 1 * 60 * 60 * 24 * 1000,
secure: true
};
res.cookie(CookieName.token, generateToken, cookieOptions);
return {
token: generateToken,
data: {
username: find.username,
_id: find._id,
email: find.email,
profilePicture: find.profilePicture,
createdAt: find.createdAt,
updatedAt: find.updatedAt,
},
};
} catch (error: any) {
return new ApolloError(
`Unable to login due to internal server error ${error.message}`
);
}
};
on frontend I am receiving this error message. Cannot read property 'cookie' of undefined

This question has been there from several months ago.
Just in case you haven't been able to fix it yet, the reason why you're not seeing the cookies in the frontend is that you're using the option httpOnly, those cookies are not supposed to be readable by the browser (they are useful to handle authentication in the server while protecting you from cross site scripting).
If you really need to read these cookies in the browser, you should set the httpOnly option to false.

Related

how to use createUploadLink and GraphQLWsLink in react

i'm trying to use createUploadLink and GraphQLWsLink in react is there any way
import * as dotenv from 'dotenv'
dotenv.config()
import { getUser, userProtect ,adminProtect,getAdmin} from "./Users/user.utilities.js";
import { typeDefs, resolvers } from './schema.js';
import { ApolloServer } from '#apollo/server';
import { createServer } from 'http';
import { expressMiddleware } from '#apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '#apollo/server/plugin/drainHttpServer';
import { makeExecutableSchema } from '#graphql-tools/schema';
import bodyParser from 'body-parser';
import express from 'express';
import { graphqlUploadExpress,GraphQLUpload} from "graphql-upload";
import * as path from 'path';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { PubSub } from 'graphql-subscriptions';
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const wsServerCleanup = useServer({schema}, wsServer);
const startApolloServer = async () => {
app.use(graphqlUploadExpress())
app.use('/images', express.static(path.join(__dirname, '/uploads/images')))
const context = async ({ req }) => {
return {
LogedInUser: await getUser(req.headers.token),
LogedInAdmin: await getAdmin(req.headers.admintoken),
userProtect,
adminProtect
}
}
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await wsServerCleanup.dispose();
}
}
}
}
],
})
await server.start();
app.use('/graphql', bodyParser.json(), expressMiddleware(server,{ context }));
}
startApolloServer()
httpServer.listen(8000, () => { console.log(`🚀 Server ready at http://localhost:4000$`) })
;
i've tried this but is not working
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:8000/graphql',
}));
const uploadLink = createUploadLink({
uri: "http://localhost:8000/graphql" });
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const authLink = setContext((_,{headers })=>{
return{
headers:{
...headers,
token:localStorage.getItem(TOKEN),
admintoken:localStorage.getItem(ADMINTOKE)
}
}
})
export const client = new ApolloClient({
link:authLink.concat(splitLink) ,
cache: new InMemoryCache(),
});
i'm trying to use createUploadLink and GraphQLWsLink in react is there any way
i'm trying to use createUploadLink and GraphQLWsLink in react is there any wayi'm trying to use createUploadLink and GraphQLWsLink in react is there any way

Next.js / Node.js (Express): Set Cookies (with httpOnly) are in the response header but not in the browser storage

Server: Node.js, express, Type-Graphql with Apollo Server
In index.ts:
import 'reflect-metadata';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { buildSchema } from 'type-graphql';
import { createConnection } from 'typeorm';
import { verify } from 'jsonwebtoken';
import coockieParser from 'cookie-parser';
import cors from 'cors';
import User from './entity/User';
import UserResolver from './resolvers';
import { createAccessToken, createRefreshToken, sendRefreshToken } from './auth';
require('dotenv').config();
const corsOptions = {
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Access-Token', 'Authorization'],
credentials: true, // this allows to send back (to client) cookies
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:3000',
preflightContinue: false,
};
(async () => {
const PORT = process.env.PORT || 4000;
const app = express();
app.use(coockieParser());
app.use(cors(corsOptions));
// -- non graphql endpoints
app.get('/', (_, res) => {
res.send('Starter endpoint');
});
app.post('/refresh_token', async (req, res) => {
const token = req.cookies.jid;
if (!token) {
return res.send({ ok: false, accessToken: '' });
}
let payload: any = null;
try {
payload = verify(token, process.env.REFRESH_TOKEN_SECRET!);
} catch (e) {
console.log(e);
return res.send({ ok: false, accessToken: '' });
}
// token is valid, and the access token can be send back
const user = await User.findOne({ id: payload.userId });
if (!user) {
return res.send({ ok: false, accessToken: '' });
}
if (user.tokenVersion !== payload.tokenVersion) {
return res.send({ ok: false, accessToken: '' });
}
sendRefreshToken(res, createRefreshToken(user));
return res.send({ ok: true, accessToken: createAccessToken(user) });
});
//--
// -- db
await createConnection();
// --
// -- apollo server settings
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver],
}),
context: ({ req, res }) => ({ req, res }),
});
await apolloServer.start();
apolloServer.applyMiddleware({
app,
cors: false,
});
// --
app.listen(PORT, () => {
console.log(`Server running on port: ${PORT}`);
});
})();
Login mutation in the UserResolver:
//..
#Mutation(() => LoginResponse)
async login(
#Arg('email') email: string,
#Arg('password') password: string,
#Ctx() { res }: AuthContext,
): Promise<LoginResponse> {
const user = await User.findOne({ where: { email } });
if (!user) {
throw new Error('Incorrect email');
}
const valid = await compare(password, user.password);
if (!valid) {
throw new Error('Incorrect password');
}
sendRefreshToken(res, createRefreshToken(user));
return {
accessToken: createAccessToken(user),
user,
};
}
//..
When handling authentification, the cookies are set in the response header as follows:
//..
export const createAccessToken = (user: User) => sign({ userId: user.id }, process.env.ACCESS_TOKEN_SECRET!, { expiresIn: '10m' });
export const createRefreshToken = (user: User) => sign({ userId: user.id, tokenVersion: user.tokenVersion }, process.env.REFRESH_TOKEN_SECRET!, { expiresIn: '7d' });
export const sendRefreshToken = (res: Response, refreshToken: string) => {
res.cookie('jid', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/refresh_token',
});
};
//..
Client: Next.js, Graphql with URQL
In _app.tsx:
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '#emotion/react';
import { createClient, Provider } from 'urql';
import theme from '../styles/theme';
import createEmotionCache from '../lib/createEmotionCache';
import '../styles/globals.css';
// Client-side cache shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface IAppProps extends AppProps {
// eslint-disable-next-line react/require-default-props
emotionCache?: EmotionCache;
}
const client = createClient({
url: 'http://localhost:4000/graphql',
fetchOptions: {
credentials: 'include',
},
});
const App = (props: IAppProps) => {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<Provider value={client}>
<CacheProvider value={emotionCache}>
<Head>
<title>Client App</title>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
</Provider>
);
};
export default App;
Login page does not rely on SSR or SSG (so it is CSR):
import React from 'react';
import LoginForm from '../components/LoginForm/LoginForm';
import Layout from '../layouts/Layout';
interface ILoginProps {}
const Login: React.FC<ILoginProps> = () => (
<Layout
showNavbar={false}
showTransition={false}
maxWidth='xs'
>
<LoginForm />
</Layout>
);
export default Login;
The mutation is used in the LoginForm component to request an access token and set refresh token in the browser cookies:
import React from 'react';
import { useRouter } from 'next/router';
import { useLoginMutation } from '../../generated/graphql';
//...
const LoginForm = () => {
//..
const [, login] = useLoginMutation();
const router = useRouter();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (disabledSubmit) {
setShowFormHelper(true);
} else {
const res = await login({
email, // from the state of the component
password,
});
if (res && res.data?.login) {
console.log(res.data.login.accessToken);
router.push('/home');
setShowFormHelper(false);
} else {
setHelper('Something went wrong');
}
}
};
//..
};
export default LoginForm;
Issue
So, the problem is that the login response has set-cookie in the header, but the cookie still isn't set in the browser:
Question
Previously, I've implemented the same authentication scheme using the same server code but create-react-app on the client. Everything worked just fine. So, why isn't it working now with next.js? What am I missing?
Work Around
I can use something like cookies-next to put cookies into the storage. The refresh token then would need to be passed in the response data:
import React from 'react';
import { useRouter } from 'next/router';
import { useLoginMutation } from '../../generated/graphql';
import { setCookies } from 'cookies-next';
//...
const LoginForm = () => {
//..
const [, login] = useLoginMutation();
const router = useRouter();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (disabledSubmit) {
setShowFormHelper(true);
} else {
const res = await login({
email, // from the state of the component
password,
});
if (res && res.data?.login) {
console.log(res.data.login.accessToken);
setCookies('jid', res.data.login.refreshToken);
router.push('/home');
setShowFormHelper(false);
} else {
setHelper('Something went wrong');
}
}
};
//..
};
export default LoginForm;
setCookie accepts options. However, the httpOnly can't be set to true in this case anyway.
Updates
It turns out everything above works in Firefox, but not in Chrome.
in res.cookie defined in the express server, use sameSite:'lax' instead of strict. this may solve the issue.

Trouble connecting to Graphql subscriptions?

I have followed Apollo docs but seem to still be having issues connecting to subscriptions. Here is my code: On the frontend, I can see it trying to connect but is logging:
WebSocket connection to 'ws://localhost:4000/subscriptions' failed:
I have been following this: https://www.apollographql.com/docs/apollo-server/data/subscriptions/ but it seems like the documentation is slightly behind so I may be missing something.
Client:
import ReactDOM from 'react-dom';
import './index.css';
import Routes from './routes';
import 'semantic-ui-css/semantic.min.css';
import { setContext } from '#apollo/client/link/context';
import { WebSocketLink } from '#apollo/client/link/ws';
import { getMainDefinition } from '#apollo/client/utilities';
import {
ApolloProvider,
ApolloClient,
HttpLink,
InMemoryCache,
split,
} from '#apollo/client';
// Http link
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// Websocket link
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/subscriptions',
options: {
reconnect: true
}
});
// Attach auth headers to requests
const middlewareLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
x_token: token ? `${token}` : "",
x_refresh_token: refreshToken ? `${refreshToken}`: ""
}
}
});
// Combine
const httpLinkWithMiddleware = middlewareLink.concat(httpLink);
// Split link - either http or ws depending on graphql
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLinkWithMiddleware,
);
// Create client with link
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
// Provide client
const App = (
<ApolloProvider client={client}>
<Routes />
</ApolloProvider>
)
// Render
ReactDOM.render(
App,
document.getElementById('root')
);
Server:
import express from 'express';
import path from 'path';
import { fileLoader, mergeTypes, mergeResolvers } from 'merge-graphql-schemas';
import { ApolloServer } from 'apollo-server-express';
import { refreshTokens } from './auth';
import models from './models';
import cors from 'cors';
import jwt from 'jsonwebtoken';
const SECRET = "";
const SECRET2 = "";
const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './schema')));
const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));
const PORT = 4000;
const app = express();
// Cors
app.use(cors('*'));
// Add tokens
const addUser = async (req, res, next) => {
const token = req.headers['x_token'];
if (token) {
try {
const { user } = jwt.verify(token, SECRET);
req.user = user;
} catch (err) {
const refreshToken = req.headers['x_refresh_token'];
const newTokens = await refreshTokens(token, refreshToken, models, SECRET, SECRET2);
if (newTokens.token && newTokens.refreshToken) {
res.set('Access-Control-Expose-Headers', 'x_token', 'x_refresh_token');
res.set('x_token', newTokens.token);
res.set('x_refresh_token', newTokens.refreshToken);
}
req.user = newTokens.user;
}
}
next();
};
app.use(addUser);
// Create server
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
path: '/subscriptions'
},
context: ({req, res, connection}) => {
const user = req.user;
return { models, SECRET, SECRET2, user };
},
});
// Apply middleware
server.applyMiddleware({ app });
// Sync and listen
models.sequelize.sync({force: true}).then(x => {
app.listen({ port: PORT }, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
}
);
});
Any help would be appreciated...
I can't see in the server declaring a websocket creation. Something like:
const WebSocket = require('ws');
const graphQLWebSocket = new WebSocket.Server({noServer: true});
Then also put in:
server.installSubscriptionHandlers(graphQLWebSocket);
After the row with server.applyMiddleware({ app });
More info here.

ReactJS(using apollo-client) not setting cookies received from ExpressJS(using apollo-server-express)

I've created simple backend api using apollo-server-express and postgresql that has two mutations: register and login. When the login mutation is used, the api creates an auth token and a refresh token and then sends them as cookies using res.cookie(...). In the frontend, I'm using apollo-client to access my GraphQL api. When I call the login mutation, everything works as expected, I receive the set-cookie header along with the requested user-details. However, the cookies don't show up in the Application tab under Cookies. Can someone please help me. I'm using the following code:
server/index.ts
import "dotenv/config";
import "reflect-metadata";
import * as express from "express";
import * as cors from "cors";
import {
ConnectionOptions,
createConnection,
getConnectionOptions
} from "typeorm";
import { buildSchema } from "type-graphql";
import { GraphQLSchema } from "graphql";
import { ApolloServer } from "apollo-server-express";
import * as path from "path";
import * as cookieParser from "cookie-parser";
import {
UserResolver,
DepartmentResolver,
CourseResolver,
SubjectResolver,
DocumentResolver
} from "./modules";
import { customAuthChecker, formatError, GQLRuntimeContext } from "./utils";
const getOptions = async () => {
console.log(`NODE_ENV: ${process.env.NODE_ENV}`);
let connectionOptions: ConnectionOptions;
connectionOptions = {
type: "postgres",
synchronize: true,
logging: false,
entities: [path.join(__dirname, "./modules/**/*entity.*")]
};
if (process.env.DATABASE_URL) {
Object.assign(connectionOptions, { url: process.env.DATABASE_URL });
} else {
// gets your default configuration
// you could get a specific config by name getConnectionOptions('production')
// or getConnectionOptions(process.env.NODE_ENV)
connectionOptions = await getConnectionOptions();
}
return connectionOptions;
};
const connect = async (): Promise<void> => {
const typeormconfig = await getOptions();
console.log(`\n`);
console.log(`\tconfiguration: `);
console.log(typeormconfig);
console.log(`\n`);
await createConnection(typeormconfig);
};
connect().then(async () => {
const schema: GraphQLSchema = await buildSchema({
resolvers: [
UserResolver,
DepartmentResolver,
CourseResolver,
SubjectResolver,
DocumentResolver
],
authChecker: customAuthChecker
});
const app: express.Application = express();
app.use(cookieParser());
const server: ApolloServer = new ApolloServer({
schema,
formatError,
context: ({ req, res }: GQLRuntimeContext): GQLRuntimeContext => ({
req,
res
})
});
server.applyMiddleware({
app,
path: "/",
cors: { origin: "*", credentials: true }
});
app.listen({ port: process.env.PORT || 4000 }, () => {
console.log(
`🚀 Server ready at http://localhost:${process.env.PORT || 4000}${
server.graphqlPath
}`
);
});
});
client/index.tsx:
import React from "react";
import ReactDOM from "react-dom";
import {
ApolloClient,
ApolloProvider,
NormalizedCacheObject,
InMemoryCache
} from "#apollo/client";
import { createHttpLink } from "apollo-link-http";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
const link: any = createHttpLink({
uri:
/*process.env.NODE_ENV === "production"
? "https://the-notebook-graph.herokuapp.com/graphql"
: */ "http://localhost:4000/graphql",
credentials: "include"
});
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById("root")
);
serviceWorker.register();
Response headers in ReactJS application
Cookies after receiving the response
I get the following error when I try to use {...credentials: "include"...} in the frontend:
Unhandled Rejection (Error): Failed to fetch

How to pass cookie from apollo-server to apollo-clenet

Mutation
Mutation: {
signUp: (_, { res }) => {
try {
res.cookie("jwt", "token", {
httpOnly: true
});
return "Amasia";
} catch (error) {
return "error";
}
};
}
Apollo-clenet-react
const [addTodo, { loading, error, data }] = useMutation(gql);
const [formSignUp, setFormSignUp] = useState({
lastName: '',
firstName: '',
password: '',
email: '',
});
const change = e => {
const { value, name } = e.target;
setFormSignUp({ ...formSignUp, [name]: value });
};
When i make a request from react.
Here is the answer I get from the server.
1)Data {"data": {"signUp": "Amasia"}}
2) Network
Application
Well when I look in Application Cookies, it is empty.
What am I doing wrong Why are cookies empty?
problem in apollo-client and apollo-server settings (apollo-cookie).
I set up like that.
apollo-client
import { render } from 'react-dom';
import React, { Suspense } from 'react';
import { ApolloClient } from 'apollo-client'
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import './i18n';
import Loading from './component/loading';
import RouteProj from './router';
const link = createHttpLink({
uri: 'http://localhost:8000/graphql',
credentials: 'include'
});
const client = new ApolloClient({
cache: new InMemoryCache(),
link,
});
render(
<ApolloProvider client={client}>
<Suspense fallback={<Loading />}>
<RouteProj/>
</Suspense>
</ApolloProvider>,
document.getElementById('root'),
);
apollo-server
import cors from "cors";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import mongoose from "mongoose";
import schema from "./schema";
import resolvers from "./resolvers";
import models from "./models";
const app = express();
var corsOptions = {
origin: "http://localhost:3000",
credentials: true
};
app.use(cors(corsOptions));
const server = new ApolloServer({
typeDefs: schema,
resolvers,
context: ({ res }) => ({
res
})
});
server.applyMiddleware({ app, path: "/graphql", cors: false });
app.listen({ port: 8000 });

Resources