SOLUTION!
Here is the solution to the problem in this original post
Original Post
Hello smart people! I'm hoping someone could help me out with something I've been struggling with for a while.
I've worked through a WesBos course (Advanced React) and it was great, and it used JWT saved as cookies for authentication. I've been working on my own project and decided to do the same thing. Initially, I was using Apollo Server 2, and I could not figure out why my cookies were not saving so I thought, "Hey I did this with a GraphQL Yoga server, so I'll just do that instead." After trying a few things with the apollo-server-express server).
Long story short, I'm still stuck. I suppose I could ask on StackOverflow but it's not nice over there! Her is what I have going on...
index.js
const { Prisma } = require('prisma-binding');
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { GraphQLServer } = require('graphql-yoga');
const { importSchema } = require('graphql-import');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const typeDefs = importSchema('./src/schema.graphql');
const Query = require('./src/resolvers/Query');
const Mutation = require('./src/resolvers/Mutation');
const db = new Prisma({
typeDefs: './generated/prisma.graphql',
endpoint: process.env.DB_ENDPOINT,
secret: process.env.DB_SECRET
});
const server = new GraphQLServer({
typeDefs,
resolvers: {
Mutation,
Query
},
context: req => ({ ...req, db })
});
server.express.use(cookieParser());
server.express.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
// add the user to future requests
req.userId = userId;
}
next();
});
server.start({ //start the GraphQL server
cors: { // only allow from frontend server (frontend_url)
credentials: true,
port: 4000,
origin: ['http://localhost:3000'],
},
}, postStart => { //callback once connection is created
console.log(`🚀 Server now running on http://localhost:${postStart.port}`);
});
My Mutation for logging in
async signin(parent, { email, password }, ctx, info) {
const user = await ctx.db.query.user({ where: { email: email } });
if (!user) {
throw new Error(`No such user found for the email ${email}`);
}
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new Error(`password is not valid!`);
}
const token = jwt.sign({ userId: user.id }, process.env.USER_SECRET);
ctx.response.cookie('token', token, {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 31,
});
console.log(ctx.response);
return user;
},
You can see right now that I have a console.log set up in my mutation to log the response. I can see that there is an actual response that states:
body:
{ operationName: 'LOGIN_MUTATION',
variables: [Object],
query:
'mutation LOGIN_MUTATION($email: String!, $password: String!) {\n signin(email: $email, password: $password) {\n id\n name\n email\n __typename\n }\n}\n' },
_body: true,
length: undefined },
locals: [Object: null prototype] {},
[Symbol(outHeadersKey)]:
[Object: null prototype] {
'x-powered-by': [ 'X-Powered-By', 'Express' ],
'access-control-allow-origin': [ 'Access-Control-Allow-Origin', 'http://localhost:3000' ],
vary: [ 'Vary', 'Origin' ],
'access-control-allow-credentials': [ 'Access-Control-Allow-Credentials', 'true' ],
'set-cookie':
[ 'Set-Cookie', 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjanRreG5kZG9henlpMGIwMG8yamwwZzg1IiwiaWF0IjoxNTU0MDc0MDU1fQ.h617eZ1LV3yUqn01jpMaTZDTUlYQmRMxwZc1VKbSHns; Max-Age=2678400; Path=/; Expires=Wed, 01 May 2019 23:14:15 GMT; HttpOnly' ] } }
So I see at the end this Set-Cookie, but no matter what I've tried, no cookie get saved :(
I even tried just replicating what I did in the course (although packages are slightly different) to have no luck.
So, smart people, what am I missing? I feel like I want to write a post on this after I learn what's up...
Update:
- I know my env variables work, I have other mutations in the app that work fine. I also get other errors if I remove them from my .env file.
- My thought is it’s something CORS related because of the server and client on different localhosts.
ryanmdoyle / thermoFisherSci
Part Inventory description tracker/editor/exporter. Allows input and exiting of part descriptions in multiple languages to export as HTML to be used in web applications.
ThermoFisher Scientific Part Descriptions
Part Inventory description tracker/editor/exporter. Allows input and exiting of part descriptions in multiple languages to export as HTML to be used in web applications.
You can start the application by:
- npm install from both frontend and backend folders.
- You'll need to create a Prisma account and deploy to prisma from backend.
- Create a .env file in backend with:
- DB_ENDPOINT (set to your prisma endpoint)
- DB_SECRET (make a random secret for prisma to use)
- USER_SECRET (a random secret for the app to use)
- COOKIE_SECRET (something random for cookie generation)
- FRONTEND_URL=http:localhost:3000
- APP_SECRET (something random for the app to use)
- Make .env in frontend
- NODE_ENV=development
- npm run dev from both frontend (localhost: 3000) and backend (localhost:4000)
- See app from locahost:3000
Top comments (4)
Hi!
Thecredentials: true
should be on the client-side, not on server-side.Edit: it should be included on both front-end (
credentials: 'include'
) and back-end (credentials: true
) - apollographql.com/docs/react/recip...I have the same problem ? Did you solved it ?
I did! Basically, it all came down to needing to pass in various options that were CORS related to both my frontend and backend. My main issue was that I was using apollo-server-express with cors middleware, but didn;'t realize that apollo-server-express had some cors built in and both were conflicting. I ended up doing:
Then my cors middleware worked and the tokens/headers were all getting passed correctly.
You can see my codebase here
ryanmdoyle / thermoFisherSci
Part Inventory description tracker/editor/exporter. Allows input and exiting of part descriptions in multiple languages to export as HTML to be used in web applications.
ThermoFisher Scientific Part Descriptions
Part Inventory description tracker/editor/exporter. Allows input and exiting of part descriptions in multiple languages to export as HTML to be used in web applications.
You can start the application by:
.
Thank you Ryan :)