在我的 MERN Stack 项目上获取 CSRF 有效失败 403,特别是在 iOS 设备上

问题描述 投票:0回答:1

我有一个 MERN Stack 项目(最初在 Vercel 上部署为两个不同的项目,但现在 Vercel 部署在前端,后端部署在 Heroku 上)。

整个应用程序运行得非常好,除了在 iOS 设备(如 iPhone 或 Mac)中,如果我执行注册或登录等发布请求,我会得到一个
csrf 验证失败 ERRBADCSRF 令牌 403 错误。它在 Windows 或 Android 上运行良好。

相关代码如下:

索引 cors/csrf 设置:

const env = process.env.NODE_ENV || 'development';
dotenv.config({ path: '.env' });

const app = express();

connectDB();

const db = admin.firestore();
app.use(bodyParser.json({ limit: '100mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' }));
app.use(cookieParser());

console.log('Environment:', process.env.NODE_ENV);


app.use(cors({
  origin: process.env.FRONTEND_URL || 'http://localhost:3000',
  credentials: true,
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
  allowedHeaders: 'Content-Type, Authorization, X-CSRF-Token, CSRF-TOKEN',
}));
console.log('CORS settings applied');





// Session middleware
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: MongoStore.create({
    mongoUrl: process.env.MONGO_URI
  }),
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: process.env.NODE_ENV === 'production' ? 'None' : 'Lax'
  },
}));


const csrfProtection = csurf({
  cookie: {
    httpOnly: true,
    secure: true, // Set to true in production
    sameSite: process.env.NODE_ENV === 'production' ? 'None' : 'Lax',
  },
});
app.use(csrfProtection);

// Middleware to set CSRF token in a cookie for all requests
app.use((req, res, next) => {
  const csrfToken = req.csrfToken();
  console.log('Generated CSRF Token:', csrfToken); // Log generated CSRF token
  res.cookie('CSRF-TOKEN', csrfToken, {
    httpOnly: false, // Allow the client to access the cookie
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'None',
  });
  next();
});

app.get('/api/csrf-token', (req, res) => {
  const csrfToken = req.csrfToken();
  console.log('Returning CSRF Token:', csrfToken); // Log token being sent to frontend
  res.json({ csrfToken });
});


app.use((err, req, res, next) => {
  if (err.code === 'EBADCSRFTOKEN') {
    console.error('CSRF validation failed:', err);
    res.status(403).json({ error: 'Session has expired or form tampered with' });
  } else {
    next(err);
  }
});




const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1000,
  message: 'Too many requests from this IP, please try again later',
});

app.use('/api/', limiter);

app.use(helmet()); // Applies various security headers

// Initialize Passport middleware
app.use(passport.initialize());
app.use(passport.session());
app.use(express.json());

前端的 auth.js:

// src/utils/api.js
import axios from 'axios';
import { BASE_URL } from '../Constants';
import { fetchCsrfToken } from './csrf';

const api = axios.create({
  baseURL: BASE_URL,
  withCredentials: true,
});

api.interceptors.request.use(
  async (config) => {
    try {
      const csrfToken = await fetchCsrfToken();
      console.log('Setting CSRF Token in request header:', csrfToken);
      config.headers['CSRF-Token'] = csrfToken;
    } catch (error) {
      console.error('Error fetching CSRF token:', error);
    }
    return config;
  },
  (error) => {
    console.error('Request setup error:', error);
    return Promise.reject(error);
  }
);

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response) {
      console.error('Response error:', error.response.status, error.response.data);
    }
    return Promise.reject(error);
  }
);

export default api;
前端的

csrf.js:

// utils/csrf.js
// utils/csrf.js
import { BASE_URL } from "../Constants";

export const fetchCsrfToken = async () => {
  try {
    const response = await fetch(`${BASE_URL}/csrf-token`, { // Correct usage of template literals
      credentials: 'include',
    });
    

    if (!response.ok) {
      throw new Error('Failed to fetch CSRF token');
    }

    const data = await response.json();
    console.log('Fetched CSRF Token on frontend:', data.csrfToken); // Log CSRF token
    return data.csrfToken;
  } catch (error) {
    console.error('Error fetching CSRF token:', error);
    throw error;
  }
};

组件上的一个简单的handleSubmit请求:

const handleSubmit = async (e) => {
        e.preventDefault();
    
        // Trim all input fields to remove leading/trailing spaces
        setFirstName(firstName.trim());
        setLastName(lastName.trim());
        setEmail(email.trim());
        setPhone(phone.trim());
    
        if (validateForm()) {
            setLoading(true);
            try {
                const formData = new FormData();
                formData.append('firstName', firstName.trim());
                formData.append('lastName', lastName.trim());
                formData.append('email', email.trim());
                formData.append('phone', phone.trim());
                formData.append('password', password);
                formData.append('cv', cv);
                formData.append('timezone', timezone);
    
                const response = await api.post('/instructors/register', formData);
                console.log('Registration successful:', response);
                navigate('/mentor/authentication');
            } catch (error) {
                console.error('Registration error:', error);
                setGeneralError('An error occurred. Please try again.');
            } finally {
                setLoading(false);
            }
        }
    };
    

我已经尝试了几乎所有我能想到的东西,包括搞乱sameSite配置、cors配置,包括标头、使用凭据、使用X-CSRF令牌作为前端的标头。我也尝试过在 Cors Origin 上添加后端域。

我还尝试检查我的域中的前端 URL。奇怪的是,当我尝试将其作为单个项目部署在 Vercel 上时(前端和后端均部署为单个项目),它运行得非常好。尽管它也适用于 iPhone,但我无法真正使用它,因为 Vercel 对某些后端功能有一些限制。我必须在不同的域上以不同的方式设置后端。

我非常需要帮助!

reactjs node.js cors csrf
1个回答
0
投票

我似乎使用了两个不同的域。对于后端,它需要是相同的域。相反,您只需要添加两个不同的子域。

否则 iOS 设备会认为它是第三方 cookie 并阻止它。

因此,只需确保您具有相同的域设置,并为前端和后端设置两个不同的子域。

© www.soinside.com 2019 - 2024. All rights reserved.