为啥使用 Axios 的 MERN 应用程序中的请求不发送 Cookie

Posted

技术标签:

【中文标题】为啥使用 Axios 的 MERN 应用程序中的请求不发送 Cookie【英文标题】:Why are Cookies Not Sent on Requests in MERN Application using Axios为什么使用 Axios 的 MERN 应用程序中的请求不发送 Cookie 【发布时间】:2020-11-21 05:02:59 【问题描述】:

我正在尝试使用 passport.jsexpress-session 按照示例 here 实现登录,但我无法持久登录。我注意到会话 cookie 不会在任何路由上发送,除非路由包含 passport.authenticate('local'),它添加了我猜的 cookie。这意味着/getUser 路由始终不返回任何用户,并且每次我登录时都会向 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);

FE 中的 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;

护照配置是:

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。

我错过了什么?

【问题讨论】:

发送请求时是否出现控制台错误? 不,请求通过正常并返回 200,但我可以看到它们上没有 cookie(除了注册和登录,我假设护照将 cookie 放在上面) 【参考方案1】:

看一眼完整的源代码,您似乎没有在向后端发出的 Axios 请求中设置 withCredentials: true。护照在高层次上是这样工作的:

    您的 /login/register 路由在响应中发回 cookie。这是由您的浏览器存储的,以供将来请求识别会话(根据您的评论,会话似乎工作正常)。

    当您想向后端发出经过身份验证的请求时,如果它是跨域的,Axios 必须在请求中显式发送回 cookie — 这就是 withCredentials 的用武之地。没有它,Axios 不会发送 cookie带有识别会话的请求。

通过设置withCredentials: true,Axios 应该在请求中发回 cookie,Passport 和 express-session 将使用它来识别用户。

【讨论】:

如果我在 localhost 和 heroku 应用程序上添加 `withCredentials: true` 我得到 CORS 策略错误,这对我来说没有意义。 Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute. 在发送 cookie 时不能使用默认的 CORS 设置,因为它是不安全的 - 请参阅 here 如何更改 localhost (同样,如果您需要启用 CORS,您可以为 Heroku 执行此操作) . 另外,我没有看到示例应用程序 (github.com/dikuw/mern-passport) 中明确设置了 withCredentials: true,它按预期工作。 好的,app.use(cors(credentials: true, origin: 'http://localhost:3000')); 没有 CORS 错误,登录不会创建额外的会话!这样一个问题就解决了。但是重新加载时仍然没有保持登录。也许是一个新问题? 并回答您关于为什么它在示例应用程序中按预期工作的问题 - 关键区别在于它在示例应用程序中被代理。换句话说,所有请求都来自localhost:8080——如果它在同一个域中,则会自动发送凭据。但是,在您的应用程序中,您的客户端和服务器在不同的端口上运行,因此不会自动发送凭据,因为这是一个跨域请求,并且对于 very good reason 安全性而言。

以上是关于为啥使用 Axios 的 MERN 应用程序中的请求不发送 Cookie的主要内容,如果未能解决你的问题,请参考以下文章

获取“被 CORS 策略阻止:请求的资源上不存在 'Access-Control-Allow-Origin' 标头。”使用 Axios 使用 MERN 堆栈

如何使用 axios 进行 OAuth 登录

在 MERN 应用程序(React 客户端)中外包 API 调用的正确方法?

为啥我在将 MERN 部署到 Heroku 期间错误地收到文件丢失错误?

使用 multer 文件输入的 axios 请求中不存在所需的请求部分“文件”

反应,节点 axios 不工作,但 fetch 工作