Экспресс / Реакция с CORS - Установка HTTP-cookie только для безопасности для React SPA - PullRequest
0 голосов
/ 23 февраля 2019

Я пытаюсь настроить простой API / SPA с использованием Express в качестве API (api.mysite.co) и React в качестве SPA (app.mysite.co) при развертывании AWS ElasticBeanstalk / S3.Для аутентификации я пытаюсь настроить JWT внутри защищенного только для HTTP cookie.Но по какой-то причине cookie никогда не попадает на клиент.

Я изменил домен на mysite.co в целях безопасности.

Вот мой файл app.js в экспрессе:

const dotenv = require('dotenv');
const express = require('express');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const helmet = require('helmet');
const passport = require('passport');
const cors = require('cors');

const usersRouter = require('./routes/users');

const env = process.env.NODE_ENV || 'development';
const port = process.env.PORT || '3000';
const app = express();

// Setup dev env variables and modules
if (env === 'development') {
  dotenv.config();
  app.use(logger('dev'));
}

app.use(helmet());

// Setup CORS requests based on environment
if (env === 'development') {
  app.use(cors());
} else {
  app.use(
    cors({
      origin: 'https://app.partsync.co',
      credentials: true,
    }),
  );
}

app.use(cookieParser());

// Handle CORS pre-flight reqests
app.options('*', cors());

app.use(passport.initialize());
require('./lib/auth/passport')(passport); // eslint-disable-line global-require

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use('/users', usersRouter);

// Catch all route that masks 404 for security
app.get('*', (req, res) => {
  res.status(401).json({ error: 'Unauthorized access' });
});

app.listen(port);

И мой файл ./routes/users с маршрутом входа.Я подтвердил, что он работает, потому что ответ отправлен, и пользователь вошел в приложение.Но по какой-то причине cookie никогда не устанавливается в chrome.

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const router = express.Router();

const mongodb = require('../db/mongodb');
const validateRegisterInput = require('../lib/validation/register');
const validateLoginInput = require('../lib/validation/login');

// // POST /users/login - User Login
router.post('/login', (req, res) => {
  mongodb.initDb().then((db) => {
    const collection = db.collection('users');
    const { errors, isValid } = validateLoginInput(req.body);

    if (!isValid) {
      return res.status(400).json(errors);
    }

    const { email, password } = req.body;

    return collection
      .find({ email })
      .limit(1)
      .toArray((connectErr, users) => {
        if (connectErr) {
          errors.email = 'Could not verify email. Please try again later.';
          return res.status(500).json(errors);
        }

        // Pull user out of array
        const user = users[0];

        if (!user) {
          errors.email = 'Username and Password do not match.';
          return res.status(400).json(errors);
        }

        return bcrypt.compare(password, user.password).then((isMatch) => {
          if (isMatch) {
            const payload = {
              id: user._id, // eslint-disable-line no-underscore-dangle
              expire_date: Date.now() + parseInt(process.env.JWT_EXPIRATION_MS, 10),
            };

            return jwt.sign(JSON.stringify(payload), process.env.PASSPORT_SECRET, (err, token) => {
              if (err) {
                return res.status(500).json({
                  password: 'There was an internal error. Please try again later',
                });
              }

              res.cookie('jwt', token, {
                domain: 'app.mysite.co',
                httpOnly: true,
                secure: true,
                maxAge: parseInt(process.env.JWT_EXPIRATION_MS, 10),
              });
              return res.status(200).json({
                name: user.name,
              });
            });
          }

          errors.password = 'Username and Password do not match.';
          return res.status(401).json(errors);
        });
      });
  });
});

module.exports = router;

В React SPA я использую Axios для выполнения вызова POST.В файле app.js я задаю:

axios.defaults.baseURL = 'https://api.mysite.co';
axios.defaults.withCredentials = true;

Затем выполняю вызов следующим образом:

/**
 * @description Logins in new user and sets their token in localStorage
 * @param {object} user Input from the login form.
 */
export const loginUser = user => dispatch => axios
  .post('/users/login', user)
  .then((res) => {
    dispatch(setCurrentUser(res.data));
  })
  .catch((err) => {
    dispatch(handleError(err));
  });

Следует отметить, что когда я отправляю запрос, обапредполетные опции и вызовы POST имеют это для заголовков запроса:

Provisional headers are shown
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: https://app.mysite.co
Referer: https://app.mysite.co/login
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36

Я провел некоторое исследование, и кажется, что когда отображается предупреждение Provisional headers are shown, это означает, что вызов API не работает правильно,Но я получаю следующий ответ от API на вызов POST:

Request URL: https://api.mysite.co/users/login
Request Method: POST
Status Code: 200 
Remote Address: 52.201.140.183:443
Referrer Policy: no-referrer-when-downgrade

access-control-allow-credentials: true
access-control-allow-origin: https://app.mysite.co
content-length: 16
content-type: application/json; charset=utf-8
date: Sat, 23 Feb 2019 15:22:33 GMT
etag: W/"10-pyeidcXwb9ByfCNz+iqLTARQNP8"
server: nginx/1.14.1
status: 200
vary: Origin
x-powered-by: Express

Provisional headers are shown
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Origin: https://app.mysite.co
Referer: https://app.mysite.co/login
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36

Показывает, что это успешно, а затем пользователь входит в систему.

Любая помощь будет принята с благодарностьютак как кажется, что я далеко из моей глубины здесь.Я не получаю никаких ошибок сервера или клиента, и из всего, что я прочитал, установка флага credentials: true на Express и Axios должна была сработать.

Спасибо!

1 Ответ

0 голосов
/ 24 февраля 2019

Таким образом, после попытки сделать что-то другое, проблема заключалась в настройке домена в res.cookie для субдомена.Я изменил этот код на:

              res.cookie('jwt', token, {
                domain: 'partsync.co',
                secure: true,
                httpOnly: true,
                maxAge: parseInt(process.env.JWT_EXPIRATION_MS, 10),
              });

, и теперь файл cookie устанавливается.

...