未设置 Cookie,即使它是响应 headers。使用 express-session 个 cookie
Cookie not set, even though it is in response headers. Using express-session cookies
问题:
尝试使用 express-session
在登录时设置 cookie,但我认为我遗漏了一些明显的东西。对登录 POST 请求的响应包括 Set-Cookie。我还将 Access-Control-Allow-Origin
和 Access-Control-Allow-Headers
设置为通配符,如下所示:
https://i.stack.imgur.com/XS0Zv.png
但是我们看到在浏览器存储中(尝试使用 Firefox 和 Chrome)什么也没有。 As shown here
我目前正在按如下方式设置我的 express-session(完整代码请参阅 post 的末尾。添加片段以便于阅读):
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
然后在我验证用户已登录后,我尝试通过以下方式设置 userId:
req.session.userId = user.id;
可能相关的信息
- 如您所见 here,这些会话已成功存储在 Mongo 中,这让我相信我至少正确地生成了会话。现在我在这里可能完全错了...
- 我的后端是
localhost:8000
上的 运行 通过:app.listen(8000);
- 我的客户 运行
http://localhost:3000/
- 尽量不将 Apollo GraphQL 用于学习目的
到目前为止我尝试过的事情:
resave
、saveUnitialized
的不同组合。
- 删除
cookie
参数。
- 停止设置 userId
- 正在重新启动浏览器和服务器
- 查看了相关的堆栈溢出 posts
请指教!甚至有关如何调试它或我可以查看的其他内容的想法也会非常有帮助!
相关代码
app.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const {v4: uuidv4} = require('uuid');
const graphqlSchema = require('./graphql/schema/index');
const graphqlResolvers = require('./graphql/resolvers/index');
const app = express();
const path = '/graphql';
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
mongoose
.connect(`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@cluster0.ccz92.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
{ useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
)
.then(() => {
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
app.use(path, graphqlHTTP({
schema: graphqlSchema,
rootValue: graphqlResolvers,
graphiql: true,
}));
app.listen(8000);
})
.catch(err => {
console.log(err);
});
graphql/resolvers/auth.js
const argon2 = require('argon2');
const jwt = require('jsonwebtoken');
const User = require('../../models/user');
module.exports = {
createUser: async args => {
try {
const existingUser = await User.findOne({
email: args.userInput.email
});
if (existingUser) {
throw new Error('User exists already.');
}
const hashedPassword = await argon2.hash(
args.userInput.password,
12
);
const user = new User({
email: args.userInput.email,
password: hashedPassword,
loggedIn: true
});
const result = await user.save();
const token = jwt.sign(
{ userId: result.id, email: result.email },
process.env.JWT_KEY,
{ expiresIn: '1h' }
);
return {
userId: result.id,
token: token,
tokenExpiration: 1
};
} catch (err) {
console.log("error in resolvers/auth.js");
throw err;
}
},
login: async (args, req) => {
const { userId } = req.session;
if (userId) {
console.log("found req.session");
return User.findOne({ _id: userId });
}
console.log("looking for user with ", args.userInput.email);
const user = await User.findOne({ email: args.userInput.email });
console.log("found user");
if (!user) {
throw new Error("User does not exist!");
}
user.loggedIn = true;
user.save();
const isEqual = await argon2.verify(user.password, args.userInput.password);
if (!isEqual) {
throw new Error ("Password is incorrect!");
}
console.log("setting session.userId");
req.session.userId = user.id;
return { ...user._doc, password: null};
},
logout: async (args, req) => {
if (!req.isAuth) {
throw new Error('Unauthenticated');
}
try {
const result = await User.findOneAndUpdate(
{ _id: req.userId },
{ loggedIn: false },
{ new: true },
);
return { ...result._doc, password: null };
} catch (err) {
console.log("logout error", err);
throw(err);
}
},
};
原来是CORS问题。我没有意识到端口意味着不同的来源。在这种情况下,我的客户端是 3000,我的服务器是 8000。
考虑到 CORS 的性质,在客户端中我需要在获取时包含 credentials
(cookie、授权 headers 或 TLS 客户端证书):
fetch(config.url.API_URL, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
},
credentials: "include",
})
这将告诉用户代理始终发送 cookie。
然后服务器端我需要设置 Access-Control-Allow-Credentials
为真:
res.setHeader('Access-Control-Allow-Credentials', true);
这将允许浏览器向前端 Javascript 代码公开响应(包含 cookie)。
由于我们使用凭据,因此我们需要指定 Access-Control-Allow-Headers
和 Access-Control-Allow-Origin
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
问题:
尝试使用 express-session
在登录时设置 cookie,但我认为我遗漏了一些明显的东西。对登录 POST 请求的响应包括 Set-Cookie。我还将 Access-Control-Allow-Origin
和 Access-Control-Allow-Headers
设置为通配符,如下所示:
https://i.stack.imgur.com/XS0Zv.png
但是我们看到在浏览器存储中(尝试使用 Firefox 和 Chrome)什么也没有。 As shown here
我目前正在按如下方式设置我的 express-session(完整代码请参阅 post 的末尾。添加片段以便于阅读):
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
然后在我验证用户已登录后,我尝试通过以下方式设置 userId:
req.session.userId = user.id;
可能相关的信息
- 如您所见 here,这些会话已成功存储在 Mongo 中,这让我相信我至少正确地生成了会话。现在我在这里可能完全错了...
- 我的后端是
localhost:8000
上的 运行 通过:app.listen(8000);
- 我的客户 运行
http://localhost:3000/
- 尽量不将 Apollo GraphQL 用于学习目的
到目前为止我尝试过的事情:
resave
、saveUnitialized
的不同组合。- 删除
cookie
参数。 - 停止设置 userId
- 正在重新启动浏览器和服务器
- 查看了相关的堆栈溢出 posts
请指教!甚至有关如何调试它或我可以查看的其他内容的想法也会非常有帮助!
相关代码
app.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const {v4: uuidv4} = require('uuid');
const graphqlSchema = require('./graphql/schema/index');
const graphqlResolvers = require('./graphql/resolvers/index');
const app = express();
const path = '/graphql';
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
mongoose
.connect(`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@cluster0.ccz92.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
{ useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
)
.then(() => {
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
app.use(path, graphqlHTTP({
schema: graphqlSchema,
rootValue: graphqlResolvers,
graphiql: true,
}));
app.listen(8000);
})
.catch(err => {
console.log(err);
});
graphql/resolvers/auth.js
const argon2 = require('argon2');
const jwt = require('jsonwebtoken');
const User = require('../../models/user');
module.exports = {
createUser: async args => {
try {
const existingUser = await User.findOne({
email: args.userInput.email
});
if (existingUser) {
throw new Error('User exists already.');
}
const hashedPassword = await argon2.hash(
args.userInput.password,
12
);
const user = new User({
email: args.userInput.email,
password: hashedPassword,
loggedIn: true
});
const result = await user.save();
const token = jwt.sign(
{ userId: result.id, email: result.email },
process.env.JWT_KEY,
{ expiresIn: '1h' }
);
return {
userId: result.id,
token: token,
tokenExpiration: 1
};
} catch (err) {
console.log("error in resolvers/auth.js");
throw err;
}
},
login: async (args, req) => {
const { userId } = req.session;
if (userId) {
console.log("found req.session");
return User.findOne({ _id: userId });
}
console.log("looking for user with ", args.userInput.email);
const user = await User.findOne({ email: args.userInput.email });
console.log("found user");
if (!user) {
throw new Error("User does not exist!");
}
user.loggedIn = true;
user.save();
const isEqual = await argon2.verify(user.password, args.userInput.password);
if (!isEqual) {
throw new Error ("Password is incorrect!");
}
console.log("setting session.userId");
req.session.userId = user.id;
return { ...user._doc, password: null};
},
logout: async (args, req) => {
if (!req.isAuth) {
throw new Error('Unauthenticated');
}
try {
const result = await User.findOneAndUpdate(
{ _id: req.userId },
{ loggedIn: false },
{ new: true },
);
return { ...result._doc, password: null };
} catch (err) {
console.log("logout error", err);
throw(err);
}
},
};
原来是CORS问题。我没有意识到端口意味着不同的来源。在这种情况下,我的客户端是 3000,我的服务器是 8000。
考虑到 CORS 的性质,在客户端中我需要在获取时包含 credentials
(cookie、授权 headers 或 TLS 客户端证书):
fetch(config.url.API_URL, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
},
credentials: "include",
})
这将告诉用户代理始终发送 cookie。
然后服务器端我需要设置 Access-Control-Allow-Credentials
为真:
res.setHeader('Access-Control-Allow-Credentials', true);
这将允许浏览器向前端 Javascript 代码公开响应(包含 cookie)。
由于我们使用凭据,因此我们需要指定 Access-Control-Allow-Headers
和 Access-Control-Allow-Origin
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');