I am trying to pass res
from my context into a resolver so that I can call context.res.cookie
in my signin function and then send an http only cookie. I have the following code which I am not seeing the cookie added on the client but the sign in function is working besides that:
const resolvers = {
Mutation: {
signin: async (_, { email, password }, context) => {
const user = await User.findOne({ email: email });
if (!user) {
throw new Error("No such user found");
}
const valid = bcrypt.compare(password, user.password);
if (!valid) {
throw new Error("Invalid password");
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET,
{
expiresIn: "30m",
});
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
return {
token,
user,
};
},
},
};
I have shortened the above code but originally I am returning the JWT token and mongodb user, I am trying to also add the http cookie of the same token (it will be a different token later when I sepearte access and refresh token).
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req, res }) => {
/* Authentication boiler plate */
return { isAuthenticated, res };
},
});
The above code is just how I am passing the res, not sure if its needed but just in case.
The following is how the function will be called from the front end:
export const Login = () => {
const SIGN_IN = gql`
mutation Signin($email: String!, $password: String!) {
signin(email: $email, password: $password) {
token
user {
id
name
email
}
}
}
`;
const [signIn, { error, loading, data }] = useMutation(SIGN_IN);
const signInFunction = async () => {
signIn({
variables: {
email: email,
password: password,
},
});
};
if (data) {
return <Navigate to="/" />
}
};
So I needed to slightly change both my client and my server to solve my issue. On the client in apollo-client I needed to change my apolloClient from this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
});
to this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
credentials: "include",
});
Now on the server I needed to add cors like this:
const server = new ApolloServer({
typeDefs,
resolvers,
cors: {
origin: "http://localhost:3000",
credentials: true,
},
context: async ({ req, res }) => {
/* insert any boilerplate context code here */
return { isAuthenticated, res };
},
});
Thus passing res to the resolver this way works perfectly fine. However when I was getting the cookie from server now it would get deleted if I refreshed the page thus I needed an explicit expiration date, thus I changed my cookie from:
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
to (24 hour expiration):
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
Some notes on this solution: On the client when you add the credentials: "include"
, you NEED to also add the cors on the backend otherwise nothing will work, however if you remove both they will communicate fine just without cookies. Also if you add the cors and not the include nothing will break but you will not receive the cookies.
Finally this post helped me find the solution, however I did not need to setup express middleware or use apollo-link-http
library as you can see above in my solution, however the post may still be helpful.