ExpressJS + ReactJS SPA - 在每个请求中生成不同的 CSRF 令牌

ExpressJS + ReactJS SPA - Different CSRF token generated in every request

我已经设置了 csurf 节点模块来为我的 ExpressJS 应用程序添加 CSRF 保护。前端是 ReactJS 单页应用程序。问题是每次 POST 请求时我都会不断收到此错误:ForbiddenError: invalid csrf token。我发现 CSRF 令牌在请求之间以某种方式发生变化并且不会持续存在。我该如何解决这个问题?这是我的 Express 服务器代码:

import * as dotenv from "dotenv";
import express, { Express } from 'express';
import morgan from 'morgan';
import helmet from 'helmet';
import cors from 'cors';
import cookieSession from 'cookie-session';
import cookieParser from 'cookie-parser';
import csrf from 'csurf';
import passport from 'passport';
import fs from 'fs';

import config from '../config.json';

dotenv.config({ path: `.env.${config.NODE_ENV}` });

const app: Express = express();

/************************************************************************************
 *                              Basic Express Middlewares
 ***********************************************************************************/

app.set('json spaces', 4);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

const useSecureCookies: boolean = (process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test');

app.use(cookieSession({
  name: 'myapp',
  keys: [process.env.COOKIE_SECRET],
  secure: useSecureCookies,
  maxAge: 24 * 60 * 60 * 1000 // 24 hours
}));

// Handle logs in console during development
if (process.env.NODE_ENV === 'development' || config.NODE_ENV === 'development') {
  app.use(morgan('dev'));
  app.use(cors());
}

// Handle security and origin in production
if (process.env.NODE_ENV === 'production' || config.NODE_ENV === 'production') {
  app.use(helmet());
}

/***********************************
 *         CSRF Protection         *
 ***********************************/
 app.use(csrf({
  cookie: {
    secure: useSecureCookies,
    httpOnly: true
  }
}));

// Add the CSRF token to every request
// Source: 
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
  res.cookie("X-CSRF-TOKEN", req.csrfToken());
  next();
});

/***********************************
 *         Authentication          *
 ***********************************/
 app.use(passport.initialize());
 app.use(passport.session());

...

export default app;

我在 app/routes/session.ts 中创建了一条新路线:

import { Router } from 'express';
import passport from 'passport';
import { BASE_ENDPOINT } from "../../constants/endpoint";
import User from '../../db/models/user';

export const router: Router = Router();

router.get(`${BASE_ENDPOINT}/sessions/sign_in`, async (req, res) => {
  res.status(200).send({
    token: req.csrfToken(),
    session: req.session?.csrfSecret
  });
});

router.post(`${BASE_ENDPOINT}/sessions/sign_in`,
  passport.authenticate('local'),
  async (req, res) => {
    res.status(200).send({
      user: (req.user as User).toJSON()
    });
  }
);

我正在使用 Ruby 控制台发出 GET 和 POST 请求来测试我服务器的 API:

  1. 我向 /sessions/sign_in 发出 GET 请求以获取 CSRF 令牌
  2. 我使用用户的电子邮件和密码向 /sessions/sign_in 发出 POST 请求。我尝试在 POST body 中包含一个带有令牌的 _csrf 字段,并在令牌中包含一个 X-CSRF-TOKEN header,但是 none的工作。

任何关于如何保留令牌的想法都将不胜感激。

我明白了。这是因为我的浏览器和服务器没有发送 cookie。这是我修复它的方法:

客户端

  1. withCredentials: true 选项添加到我的 axios 实例中:
const axiosInstance = axios.create({
  baseURL: process.env.SERVER_API_BASE_URL,
  withCredentials: true
});

服务器

  1. 更新 CORS 以向客户端发送凭据。在 server.ts:
import cors from 'cors';

...

app.use(cors({
  credentials: true,
  origin: "http://localhost:8080" // The host name of the client
}));

...

现在我要做的就是将 CSRF 令牌从服务器发送到客户端,然后将其包含在我的请求中。