0

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="/" />
  }
  
};
Caleb
  • 439
  • 8
  • 29
  • Yes I believe so? I am a bit confused by the question, I am returning just a JWT token and user.id but I am using promises inside the signin function to retrieve from the database, I can upload the code above the return if it helps – Caleb Sep 29 '22 at 00:07
  • I updated how it is called from the frontend, I am not sure it should return a promise – Caleb Sep 29 '22 at 00:20
  • ah, signin is async because I have promises before the final return, such as: `const user = await User.findOne({ email: email });`, However the final return of signin is itself not a promise. As far as whether or not the function is called I do not understand what you mean. Are you asking if I am able to successfully call the function? Then the answer is yes, if you mean something else, can you please elaborate. – Caleb Sep 29 '22 at 00:28
  • Yes I am able to receive the token and user that is being returned I can receive that through `data` from the `SIGN_IN` mutation, however the only cookie I see after the function takes place is one I put there on the client side. Thus I am not seeing the cookie from server be added. – Caleb Sep 29 '22 at 00:36
  • I must misunderstand the issue then - I'll remove my comments – Jaromanda X Sep 29 '22 at 00:43

1 Answers1

1

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.

Caleb
  • 439
  • 8
  • 29