为什么在使用 Axios 的 MERN 应用程序中不根据请求发送 Cookie
Why are Cookies Not Sent on Requests in MERN Application using Axios
我正在尝试按照示例 here 使用 passport.js
和 express-session
来实现登录,但我无法持久登录。我注意到会话 cookie 不会在任何路由上发送,除非路由包含 passport.authenticate('local')
,我猜这会添加 cookie。这意味着 /getUser
路由总是 returns 没有用户,并且每次我登录时都会向 MongoDB 存储添加一个新会话。
我的路线如下:
router.get('/getUser', userController.getCurrentUser);
router.post('/register', userController.register, passport.authenticate('local'), authController.login);
router.post('/login', passport.authenticate('local'), authController.login);
router.post('/logout', authController.logout);
控制函数是:
exports.getCurrentUser = (req, res) => {
if (req.user) {
return res.send(req.user);
} else {
res.json({ error: 'No user found' });
};
};
exports.register = async (req, res, next) => {
const user = new User({
email: req.body.email,
name: req.body.name,
});
const register = promisify(User.register, User);
await register(user, req.body.password);
next();
};
exports.login = (req, res) => {
req.login(req.user, function(err) {
if (err) { res.json({ error: err }); }
return res.send(req.user);
});
};
exports.logout = (req, res) => {
if (req.user) {
req.logout();
res.send({ msg: 'logged out' });
} else {
res.send({ msg: 'no user to log out' })
};
};
用户模型是:
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
const validator = require('validator');
const passportLocalMongoose = require('passport-local-mongoose');
const userSchema = new Schema({
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
validate: [validator.isEmail, 'Please enter a valid email'],
required: 'Please enter an email address'
},
name: {
type: String,
required: 'Please enter a name',
trim: true
},
resetPasswordToken: String,
resetPasswordExpires: Date,
isAdmin: {
type: Boolean,
default: false
},
}, { timestamps: true });
userSchema.plugin(passportLocalMongoose, { usernameField: 'email' });
module.exports = mongoose.model('User', userSchema);
前端中的axios模块:
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000/api',
});
export const getUser = () => api.get('/getUser');
export const register = payload => api.post('/register', payload);
export const login = payload => api.post('/login', payload);
export const logout = payload => api.post('/logout', payload);
const apis = {
getUser,
register,
login,
logout,
};
export default apis;
Passport 配置为:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, { _id: user._id });
});
passport.deserializeUser((id, done) => {
User.findOne({ _id: id }, 'username', (err, user) => {
done(null, user)
});
});
const strategy = new LocalStrategy(
{ usernameField: 'email' },
function(email, password, done) {
User.findOne({ email: email }, (err, user) => {
if (err) {
return done(err);
}
return done(null, user);
});
}
);
passport.use(strategy);
module.exports = passport;
最后服务器配置是:
const express = require('express');
const session = require('express-session');
const mongoose = require('mongoose');
const MongoStore = require('connect-mongo')(session);
const cors = require('cors');
const bodyParser = require('body-parser');
const promisify = require('es6-promisify');
// import all models
require('./models/User');
const passport = require('./handlers/passport');
const db = require('./database');
const router = require('./routes');
const app = express();
// enable CORS for all origins to allow development with local server
app.use(cors({credentials: true}));
// use bodyParser to allow req.params and req.query
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(session({
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
// passport.js to handle logins
app.use(passport.initialize());
app.use(passport.session());
// pass variables on all requests
app.use((req, res, next) => {
res.locals.user = req.user || null;
res.locals.session = req.session;
next();
});
// promisify some callback based APIs
app.use((req, res, next) => {
req.login = promisify(req.login, req);
next();
});
app.use('/api', router);
app.get('/', (req, res) => {
res.send('This is the Hely Cosmetics website backend/API');
})
app.set('port', process.env.PORT || 8000);
const server = app.listen(app.get('port'), () => {
console.log(`Express running on port ${server.address().port}`);
});
我认为它可能是 cors,但我尝试 app.use(cors({credentials: true}));
结果相同,并验证了部署为 Heroku 应用程序的前端和后端的相同问题。
完整的源代码可用 here。
我错过了什么?
从完整的源代码来看,您似乎没有在向后端发出的 Axios 请求中设置 withCredentials: true
。护照,在高层次上,是这样工作的:
您的 /login
和 /register
路由在它们的响应中发回一个 cookie。这是由您的浏览器存储的,用于将来识别会话的请求(根据您的评论,它似乎工作正常)。
当你想向后端发出经过身份验证的请求时,如果它是跨源的,Axios 必须在请求中显式地发回 cookie——这就是 withCredentials
的用武之地。没有它,Axios 不会发送带有识别会话请求的 cookie。
通过设置 withCredentials: true
,Axios 应该在请求中发回 cookie,Passport & express-session 将使用它来识别用户。
我正在尝试按照示例 here 使用 passport.js
和 express-session
来实现登录,但我无法持久登录。我注意到会话 cookie 不会在任何路由上发送,除非路由包含 passport.authenticate('local')
,我猜这会添加 cookie。这意味着 /getUser
路由总是 returns 没有用户,并且每次我登录时都会向 MongoDB 存储添加一个新会话。
我的路线如下:
router.get('/getUser', userController.getCurrentUser);
router.post('/register', userController.register, passport.authenticate('local'), authController.login);
router.post('/login', passport.authenticate('local'), authController.login);
router.post('/logout', authController.logout);
控制函数是:
exports.getCurrentUser = (req, res) => {
if (req.user) {
return res.send(req.user);
} else {
res.json({ error: 'No user found' });
};
};
exports.register = async (req, res, next) => {
const user = new User({
email: req.body.email,
name: req.body.name,
});
const register = promisify(User.register, User);
await register(user, req.body.password);
next();
};
exports.login = (req, res) => {
req.login(req.user, function(err) {
if (err) { res.json({ error: err }); }
return res.send(req.user);
});
};
exports.logout = (req, res) => {
if (req.user) {
req.logout();
res.send({ msg: 'logged out' });
} else {
res.send({ msg: 'no user to log out' })
};
};
用户模型是:
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
const validator = require('validator');
const passportLocalMongoose = require('passport-local-mongoose');
const userSchema = new Schema({
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
validate: [validator.isEmail, 'Please enter a valid email'],
required: 'Please enter an email address'
},
name: {
type: String,
required: 'Please enter a name',
trim: true
},
resetPasswordToken: String,
resetPasswordExpires: Date,
isAdmin: {
type: Boolean,
default: false
},
}, { timestamps: true });
userSchema.plugin(passportLocalMongoose, { usernameField: 'email' });
module.exports = mongoose.model('User', userSchema);
前端中的axios模块:
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000/api',
});
export const getUser = () => api.get('/getUser');
export const register = payload => api.post('/register', payload);
export const login = payload => api.post('/login', payload);
export const logout = payload => api.post('/logout', payload);
const apis = {
getUser,
register,
login,
logout,
};
export default apis;
Passport 配置为:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, { _id: user._id });
});
passport.deserializeUser((id, done) => {
User.findOne({ _id: id }, 'username', (err, user) => {
done(null, user)
});
});
const strategy = new LocalStrategy(
{ usernameField: 'email' },
function(email, password, done) {
User.findOne({ email: email }, (err, user) => {
if (err) {
return done(err);
}
return done(null, user);
});
}
);
passport.use(strategy);
module.exports = passport;
最后服务器配置是:
const express = require('express');
const session = require('express-session');
const mongoose = require('mongoose');
const MongoStore = require('connect-mongo')(session);
const cors = require('cors');
const bodyParser = require('body-parser');
const promisify = require('es6-promisify');
// import all models
require('./models/User');
const passport = require('./handlers/passport');
const db = require('./database');
const router = require('./routes');
const app = express();
// enable CORS for all origins to allow development with local server
app.use(cors({credentials: true}));
// use bodyParser to allow req.params and req.query
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(session({
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
// passport.js to handle logins
app.use(passport.initialize());
app.use(passport.session());
// pass variables on all requests
app.use((req, res, next) => {
res.locals.user = req.user || null;
res.locals.session = req.session;
next();
});
// promisify some callback based APIs
app.use((req, res, next) => {
req.login = promisify(req.login, req);
next();
});
app.use('/api', router);
app.get('/', (req, res) => {
res.send('This is the Hely Cosmetics website backend/API');
})
app.set('port', process.env.PORT || 8000);
const server = app.listen(app.get('port'), () => {
console.log(`Express running on port ${server.address().port}`);
});
我认为它可能是 cors,但我尝试 app.use(cors({credentials: true}));
结果相同,并验证了部署为 Heroku 应用程序的前端和后端的相同问题。
完整的源代码可用 here。
我错过了什么?
从完整的源代码来看,您似乎没有在向后端发出的 Axios 请求中设置 withCredentials: true
。护照,在高层次上,是这样工作的:
您的
/login
和/register
路由在它们的响应中发回一个 cookie。这是由您的浏览器存储的,用于将来识别会话的请求(根据您的评论,它似乎工作正常)。当你想向后端发出经过身份验证的请求时,如果它是跨源的,Axios 必须在请求中显式地发回 cookie——这就是
withCredentials
的用武之地。没有它,Axios 不会发送带有识别会话请求的 cookie。
通过设置 withCredentials: true
,Axios 应该在请求中发回 cookie,Passport & express-session 将使用它来识别用户。