Не удается настроить куки. Node/React/Vercel/Render

Вопросы и ответы

Я не писал бэкенд и у меня нет опыта работы с ним. Это был проект из двух человек, и теперь я пытаюсь получить этот опыт, развернув его.
У меня бэкенд развернут на Render, фронтенд на Vercel.
Все работало в разработке и в предварительном просмотре. Только после развертывания возникли проблемы с куками, что означает, что, чтобы протестировать код, мне приходится постоянно развертывать его заново, так как они не появляются при тестировании.

const path = require('path');

const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const socket = require('socket.io');
const multer = require('multer');
const cors = require('cors');
require('dotenv').config();

const User = require('./models/user');

const MONGODB_URI = process.env.MONGO_KEY;
const app = express();
const http = require('http');
const server = http.createServer(app);
const io = socket(server);

let users = [];

const addUser = (userId, socketId) => {
  !users.some((user) => user.userId === userId) && users.push({ userId, socketId });
};

const removeUser = (socketId) => {
  users = users.filter((user) => user.socketId !== socketId);
};

const getUser = (userId) => {
  return users.find((user) => user.userId === userId);
};

io.on('connection', (socket) => {
  // когда подключено
  console.log('пользователь подключен.');

  // взять userId и socketId от пользователя
  socket.on('addUser', (userId) => {
    addUser(userId, socket.id);
    io.emit('getUsers', users);
  });

  // отправка и получение сообщений
  socket.on('sendMessage', ({ senderId, receiverId, text }) => {
    const user = getUser(receiverId);
    io.to(user.socketId).emit('getMessage', {
      senderId,
      text,
    });
  });

  // когда отключен
  socket.on('disconnect', () => {
    console.log('пользователь отключен!');
    removeUser(socket.id);
    io.emit('getUsers', users);
  });
});

const store = new MongoDBStore({
  uri: MONGODB_URI,
  collection: 'sessions',
});

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'images');
  },
  filename: (req, file, cb) => {
    cb(null, new Date().toISOString().replace(/:/g, '-') + '-' + file.originalname);
  },
});

const fileFilter = (req, file, cb) => {
  if (
    file.mimetype === 'image/png' ||
    file.mimetype === 'image/jpg' ||
    file.mimetype === 'image/jpeg'
  ) {
    cb(null, true);
  } else {
    cb(null, false);
  }
};

app.set('views', 'views');
app.set('view engine', 'ejs');

const marketRoutes = require('./routes/market');
const adminRoutes = require('./routes/admin');
const authRoutes = require('./routes/auth');
const messageRoutes = require('./routes/message');
const chatRoutes = require('./routes/chat');
const user = require('./models/user');

app.use(
  cors({
    origin: process.env.CLIENT_URL, // настроено на прием учетных данных из фронтенда, чтобы куки работали
    credentials: true,
  })
);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer({ storage: storage, fileFilter: fileFilter }).single('image'));
app.use(express.static(path.join(__dirname, '/public')));
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use(
  session({
    secret: 'мой секрет',
    resave: false,
    saveUninitialized: false,
    store: store,
    cookie: {
      maxAge: 30 * 24 * 60 * 60 * 1000,
      httpOnly: true,   
      secure: true,  
      sameSite: 'None', 
    },
  })
);

app.use((req, res, next) => {
  res.locals.isAuthenticated = req.session.isLoggedIn;
  next();
});

app.use((req, res, next) => {
  if (!req.session.user) {
    return next();
  }
  User.findById(req.session.user._id)
    .then((user) => {
      if (!user) {
        return next();
      }
      req.user = user;
      next();
    })
    .catch((err) => {
      console.log(err);
    });
});

app.use(marketRoutes);
app.use(adminRoutes);
app.use(authRoutes);
app.use(messageRoutes);
app.use(chatRoutes);

mongoose
  .connect(MONGODB_URI)
  .then((result) => {
    server.listen(3000);
    console.log('подключено!');
  })
  .catch((err) => {
    console.log(err);
  });

Так что после того, как я несколько раз обошелся с несколькими ИИ, они все время повторяют одно и то же: добавить эти строки, которых изначально не было:

                   httpOnly: true,   
                     secure: true,  
                   sameSite: 'None', 

Это не сработало. Без этих строк приложение имело полноценную функциональность только в браузере Firefox. В любом другом браузере, который я тестировал, вы можете видеть данные БД, можете зарегистрироваться/войти (POST запросы), но не можете выполнять любые другие запросы, потому что контроллер будет проверять req.session.user практически в каждом контроллере. Это зависит от куки для правильной работы. Клиентская сторона будет использовать некоторые хуки для выполнения запросов, которые вызовут эту функцию

export interface Fetch {
  params: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  data?: object;
}

export const fetchData = async ({ params, method, data }: Fetch) => {
  const isFormData = data instanceof FormData;
  const body = data ? (isFormData ? data : JSON.stringify(data)) : null;

  const response = await fetch(import.meta.env.VITE_SERVER_URL + params, {
    method,
    headers: isFormData ? undefined : { 'Content-Type': 'application/json' },
    credentials: 'include', // отправить куки на бэкенд
    body,
  });

  const resData = await response.json();

  if (!response.ok) {
    throw resData;
  }

  return resData;
};

Cors настроен для приема учетных данных. Учетные данные установлены в true на клиентской стороне. Все работает в Dev/Preview и Firefox с кодом приложения, как он есть:

app.use(
  session({
    secret: 'мой секрет',
    resave: false,
    saveUninitialized: false,
    store: store,
    cookie: {
      maxAge: 30 * 24 * 60 * 60 * 1000, 
    },
  })
);

Но куки не работают в любом браузере, кроме Firefox. Тогда добавление дополнительной конфигурации куки предотвращает ее работу в любой среде вообще.

Я пытался изменить конфигурацию куки и развернуть снова, но это только сделало его менее функциональным. Я бы подумал, что настройка правильная, но я не могу получить дальнейшую помощь по этому вопросу. В настоящее время я мог бы развернуть ее без конфигурации куки и просто оставить заметку, что это полностью функционирует только в Firefox.

Ответ

Проблема, с которой вы сталкиваетесь, связана с настройками cookie и CORS в вашем приложении. Вот несколько шагов, которые помогут устранить проблемы с cookies, особенно когда приложение развернуто на Render и Vercel.

1. Проверка настроек CORS

Убедитесь, что ваш сервер правильно настроен для работы с CORS. У вас уже стоит credentials: true, что правильно. Вот пример, как это можно настроить:

app.use(
  cors({
    origin: process.env.CLIENT_URL, // ваш URL Vercel приложения
    credentials: true,
  })
);

Ваши настройки cookie должны учитывать использование secure, httpOnly и sameSite, особенно если ваше приложение должно быть доступно через HTTPS. Вам нужно убедиться, что secure: true работает только тогда, когда ваше приложение развернуто на HTTPS. Попробуйте следующее:

app.use(
  session({
    secret: 'my secret',
    resave: false,
    saveUninitialized: false,
    store: store,
    cookie: {
      maxAge: 30 * 24 * 60 * 60 * 1000,
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production', // Убедитесь, что secure установлено в true только в продакшн
      sameSite: 'None', // Браузеры, кроме Firefox, требуют это значение для ключевых функциональностей
    },
  })
);

3. Убедитесь, что вы используете правильные запросы

Убедитесь, что ваши запросы с клиента действительно отправляют необходимые cookies. Например, в вашем коде:

const response = await fetch(import.meta.env.VITE_SERVER_URL + params, {
  method,
  headers: isFormData ? undefined : { 'Content-Type': 'application/json' },
  credentials: 'include', // Это важно для отправки cookies
  body,
});

Это правильно, так как credentials: 'include' позволяет отправлять cookies при запросах к вашему серверу.

4. Проверьте среду разработки

Иногда проблемы с cookie могут возникнуть из-за локальной среды разработки. Убедитесь, что:

  • Ваши URL-адреса совпадают (например, если у вас есть http://localhost:3000 на сервере и http://localhost:3001 на клиенте, это не сработает без дополнительных настроек).
  • Проверьте настройки вашего браузера, возможно, он блокирует cookies.

5. Логи и отладка

Добавьте дополнительные логи на серверную часть, чтобы понять, что происходит с вашей сессией и cookies. Это может быть полезно для диагностики:

app.use((req, res, next) => {
  console.log('Session:', req.session);
  console.log('Cookies:', req.cookies);
  next();
});

6. Тестирование на разных браузерах

Некоторые браузеры могут по-разному обрабатывать cookies, особенно в более строгих настройках конфиденциальности. Проверьте:

  • Google Chrome
  • Safari
  • Edge

Сравните, как cookies обрабатываются в каждом из них. Возможно, существует разница в использовании Cookies, связанная с конфиденциальностью или безопасностью.

Заключение

Если предыдущие шаги не помогли решить проблему, попробуйте каждый из изменений по отдельности и тестируйте развертывание, чтобы более точно определить, какое изменение влияет на функциональность.

Если у вас все еще возникают проблемы, пожалуйста, предоставьте больше информации о том, что именно не так—например, сообщения об ошибках или логи сервера—чтобы мы могли помочь еще больше.

Оцените материал
Добавить комментарий

Капча загружается...