How to solve: "ForbiddenError: invalid csrf token"

How to solve: "ForbiddenError: invalid csrf token"

我在设置 csrf 时遇到问题。我希望有人能指出我正确的方向。

我正在使用 next.js 和 express.js。

当我刷新页面时发生以下情况:

  1. 我得到一个 _csurf cookie(开发工具 > 应用程序 > cookie)
  2. 我的控制台中记录了一个 csrf 令牌(-> 请参阅从上下文中截取的最后代码)
  3. 当我发出 POST 请求时(-> 请参阅 routes/index.js f.ex. "/aignupQ"),我收到错误消息“无效的 csurf 令牌”;在请求中 header 我可以看到 _csrf cookie
  4. 当我刷新页面并再次发出 POST 请求时,一切正常。

我真的被错误弄糊涂了,真的不明白哪里出了问题。这是一些相关代码:

server.js:

require("dotenv").config();
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const routes = require('./routes');

const csrf = require("csurf");
const csrfProtection = csrf({
  cookie: true,
});


//next.js configuration
const dev = process.env.NODE_DEV !== "production";
const nextApp = next({ dev });
const port = 3000;
const handle = nextApp.getRequestHandler();

nextApp.prepare().then(() => {
  const app = express();

  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(cors());
  app.use(cookieParser());
  app.use((err, req, res, next) => {
    res.status(500).send("Something went wrong!");
  });

  app.use(csrfProtection);
  app.use('/api', routes);

  app.get("*", (req, res) => {
    return handle(req, res);
  });

  //start server
  app.listen(port, (err) => {
    if (err) throw err;
    console.log(`listening on port ${port}`);
  });
});  


        

routes/index.js:

const express = require('express')
const router = express.Router()
const getCsrfToken = require('../controllers/csrf')
const postSignupQ = require("../controllers/postSignupQ");
const attachUser = require("../middleware/attachUser");

router.get("/csrfToken", getCsrfToken.getCsrfToken);
router.use(attachUser);
router.post("/signupQ", postSignupQ.postSignupQ);

module.exports = router

controllers/csrf.js

const getCsrfToken = (req, res) => {
  res.json({ csrfToken: req.csrfToken() }); 
};

module.exports = { getCsrfToken };

context - 这里我 console.log(csrf):

   useEffect(() => {
    const getCsrfToken = async() => {
      const { data } = await axios.get('api/csrfToken');
      console.log("csurf", data);
      axios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
    };

    getCsrfToken();
  }, []); 
 

我不明白为什么我会收到错误消息,当我第一次发出 POST 请求时以及当我刷新页面时一切正常。有什么问题,我该如何解决?

编辑
感谢 Jack Yu,上面的代码片段可以正常工作。也许它可以帮助别人..

编辑

我还发现您的 api 路径可能有误。在你的 axios 中是 await axios.get('csrfToken')。但是,我在您的路由器中看到 /api/csrfToken。将其更改为 await axios.get('/api/csrfToken')

原答案

在 csurf 包中,当您多次在中间件中使用 csurf({cookie: true}) 和 cookie 模式时,它会破坏 csrf 令牌作为响应 header 第一次 post。您可以在CSRF doesn't work on the first post attempt中查看更多详细信息,我在post中解释了原因。因此,您可以使用两种解决方案。

解决方案 1

根据评论,你在server.jsrouter/index.js中使用了app.use(csruf({cookie: true}))。删除 router/index.js 中的以下行。当你在 server.js 中设置 csruf 时,你可以在 controllers/csrf.js 中使用 req.csrfToken() 而无需再次设置 csruf。

const csrf = require("csurf");
const csrfProtection = csrf({
  cookie: true,
});

router.use(csrfProtection);

解决方案 2

您需要使用 express-session 包。在 csurf 之前添加以下代码。如果您的 routes/index.js 中有 .use(csrf({cookie: true})),请将其删除。

const session = require('express-session')
// mark1: change it to false
const csrfProtection = csrf({
  cookie: false,
});


// blablabla ... other code

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());

// mark2: put here
app.use(session({
    name: "test",
    secret: "test",
    cookie: { maxAge: 3 * 60 * 60 * 1000 },
    resave: false,
    saveUninitialized: false
}))
// put here

app.use((err, req, res, next) => {
    res.status(500).send("Something went wrong!");
});

app.use(csrfProtection);
app.use('/api', routes);

然后在所有csurf设置中将{cookie: true}更改为{cookie: false}。当你使用session模式时,你可以在中间件中多次使用csruf({cookie: false})

我们需要在 header 中传递一个 cookie,如下所示:

headerMaps.put("cookie", "_csrf=value; connect.sid=value; csrfToken=value")