NodeJS csrf保护仅在一种POST请求表单上给ForbiddenError无效的csrf令牌

时间:2019-10-22 22:23:27

标签: node.js express passport.js csrf ejs

在过去的几个小时中,我一直未能成功在Google上找到以下问题的解决方案:

我设置了csurf,并且运行良好。它适用于与注册/登录用户相关的POST请求。我正在尝试在用户个人资料中创建一个表单,以更新用户的数据,但是当我点击“提交”时,我得到了ForbiddenError: invalid csrf token

在进一步测试中,csrt令牌是在个人资料页面上创建的,但是由于某种原因,它是无效的。

(我最近才开始学习编程,如果错过了一些内容,我愿意提供其他信息。)

使用的工具:

  • Node.js v10.16.3
  • Express v4.17.1
  • ejs v2.7.1
  • csurf v1.10.0
  • 快速会话v1.16.2
  • 护照v ^ 0.4.0
  • cookie解析器1.4.4
  • body-parser v1.19.0

我的app.js

let express = require("express"),
app = express(),
bodyParser = require("body-parser"),
cookieParser = require('cookie-parser'),
session = require("express-session"),
mongoose = require("mongoose"),
passport = require("passport"),
flash = require('connect-flash'),
validator = require('express-validator'),
LocalStrategy = require("passport-local"),
csrf = require('csurf'),
csrfProtection = csrf({ cookie: true }),
MongoStore = require('connect-mongo')(session);

let indexRoutes = require('./routes/index');
let userRoutes = require('./routes/user');
let User = require("./models/user");

// APP CONFIGURATION
mongoose.connect("mongodb://localhost:27017/azax", { useNewUrlParser: true, useUnifiedTopology: true, }).then(() => {
    console.log("Connected to MongoDB");
}).catch((error) => {
    console.log("Something is wrong...");
});


require('./config/passport');

// View engine setup
app.set("view engine", "ejs");
app.use(express.static(__dirname + "/public"));

// Initial setup
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(validator());

// Session setup
app.use(cookieParser());
app.use(session({
    secret: 'somesecretforbytox',
    resave: false,
    saveUninitialized: false
}));
app.use(flash());

// Initialize passport
app.use(passport.initialize());
app.use(passport.session());
app.use(csrf());

// CSRF
app.use(function (req, res, next) {
    var token = req.csrfToken();
    res.cookie('XSRF-TOKEN', token);
    res.locals.csrfToken = token;
    next();
});

app.use(function (req, res, next) {
    res.locals.currentUser = req.user;
    res.locals.session = req.session;
    next();
});

// ======================
// Connect to route files
// ======================
app.use('/user', userRoutes);
app.use(indexRoutes);

app.listen(3033, function () {
    console.log("Listening at port 3033...");
});

我的password.js:

let passport = require('passport');
let User = require('../models/user');
let LocalStrategy = require('passport-local').Strategy;

passport.serializeUser(function (user, done) {
  done(null, user.id);
});

passport.deserializeUser(function (id, done) {
  User.findById(id, function (err, user) {
    done(err, user);
  });
});

passport.use('local-signup', new LocalStrategy({
  usernameField: 'email',
  passwordField: 'password',
  passReqToCallback: true
}, function (req, email, password, done) {
  req.checkBody('username', 'Invalid username').notEmpty();
  req.checkBody('email', 'Invalid email').notEmpty().isEmail();
  req.checkBody('password', 'Invalid password').notEmpty().isLength({ min: 4 });
  let errors = req.validationErrors();
  if (errors) {
    let messages = [];
    errors.forEach(function (error) {
      messages.push(error.msg);
    });
    return done(null, false, req.flash('error', messages));
  }
  User.findOne({ 'email': email }, function (err, user) {
    if (err) {
      return done(err);
    }
    if (user) {
      return done(null, false, { message: 'Вече има акаунт с този имейл.' })
    }
    let username = req.body.username;
    let newUser = new User();
    newUser.username = username;
    newUser.email = email;
    newUser.password = newUser.encryptPassword(password);
    newUser.country = 'България';
    newUser.save(function (err, result) {
      if (err) {
        return done(err);
      }
      return done(null, newUser);
    });
  });
}));

passport.use('local-signin', new LocalStrategy({
  usernameField: 'email',
  passwordField: 'password',
  passReqToCallback: true
}, function (req, email, password, done) {
  // req.checkBody('username', 'Invalid username').notEmpty();
  req.checkBody('email', 'Invalid email').notEmpty();
  req.checkBody('password', 'Invalid password').notEmpty();
  let errors = req.validationErrors();
  if (errors) {
    let messages = [];
    errors.forEach(function (error) {
      messages.push(error.msg);
    });
    return done(null, false, req.flash('error', messages));
  }
  User.findOne({ 'email': email }, function (err, user) {
    if (err) {
      return done(err);
    }
    if (!user) {
      return done(null, false, { message: 'Акаунтът не е намерен.' });
    }
    if (!user.validPassword(password)) {
      return done(null, false, { message: 'Грешна парола.' });
    }
    return done(null, user);
  });
}));

HTML ...:

<form action="/user/profile" method="POST" class="form-validate form-horizontal" enctype="multipart/form-data">
    <fieldset>
        <legend>Edit address</legend>
        <!-- email -->
        <div class="control-group">
            <div class="control-label">
                <label id="jform_email1-lbl" for="jform_email" class="hasPopover required" title="" data-content="Enter new email address." data-original-title="Email Address">
                    Email<span class="star">&nbsp;*</span></label>
            </div>
            <div class="controls">
                <input type="email" name="email" class="validate-email required" id="jform_email" value="<%= (typeof currentUser.email != 'undefined' ? currentUser.email : '') %>" size="30" autocomplete="email" required aria-required="true">
            </div>
        </div>
        <!-- name -->
        <div class="control-group">
            <div class="control-label">
                <label id="jform_fname-lbl" for="jform_fname" class="hasPopover required" title="" data-content="Enter new name." data-original-title="Name">
                    Name<span class="star">&nbsp;*</span></label>
            </div>
            <div class="controls">
                <input type="text" name="firstName" id="jform_fname" value="<%= (typeof currentUser.firstName != 'undefined' ? currentUser.firstName : '') %>" class="required" size="30" required aria-required="true">
            </div>
        </div>
    </fieldset>

    <div class="form-actions">
        <input type="hidden" name="_csrf" value="<%= csrfToken %>">
        <button type="submit" class="btn btn-primary validate">
            <span>Save</span>
        </button>
        <a class="btn" href="/" title="Cancel">Cancel</a>
    </div>
</form>

user.js:

let express = require('express'),
    router = express.Router(),
    csrf = require('csurf'),
    csrfProtection = csrf(),
    passport = require('passport');

router.use(csrfProtection);
let User = require("../models/user");

// user profile
router.get("/profile", isLoggedIn, csrfProtection, function (req, res) {
    res.render("user/profile", { csrfToken: req.csrfToken(), currentUser: req.user });
});

router.post('/profile', (req, res) => {
    updateRecord(req, res);
    res.redirect('/profile');
});
// update user data
function updateRecord(req, res) {
    User.findOne({ _id: req.user.id }, (err, doc) => {
        doc.name = req.body.name;
        doc.save(function (err, doc) {
        });
    });
}
router.get("/profile/edit", isLoggedIn, csrfProtection, function (req, res) {
     res.render("user/edit", { csrfToken: req.csrfToken(), currentUser: req.user });
});

// sign up form works with csrf

// signup form
router.get("/signup", csrfProtection, function (req, res) {
    let messages = req.flash('error');
    res.render("user/signup", { csrfToken: req.csrfToken(), messages: messages, hasErrors: messages.length > 0 });
});

// ... more routes

    module.exports = router;

// middleware
function isLoggedIn(req, res, next) {
    if (req.isAuthenticated()) {
        return next();
    }
    res.redirect('/user/login');
}

1 个答案:

答案 0 :(得分:0)

对我有用的是将以下隐藏字段添加到每种表单中:

<input type="hidden" name="_csrf" value="<%= csrfToken %>">

并将令牌发送到显示表单的模板页面的每个获取请求。

{csrfToken: req.csrfToken()}

喜欢

router.get('/user/login',function(req,res){
    res.render('pages/login', {csrfToken: req.csrfToken()});
})

这修复了整个应用程序中的错误。