使用 Express.JS + Nginx 设置 cookie 时遇到问题

use*_*061 6 javascript nginx node.js express express-session

我正在将前端与后端存储库分离。为此,我将服务器托管在家庭网络上的个人 Raspberry Pi 3 上。我的前端由 Netlify 托管。我遇到了一个问题,尽管我可以在 Postman 中看到 cookie 设置,但我无法使用 Express.Js 在客户端上设置 cookie。

我当前的设置如下:

FE (Netlify - example.com) -> Nginx (Reverse Proxy - api.example.com) -> Node.js (Express - listening for http)
Run Code Online (Sandbox Code Playgroud)

我无法登录使用 Express-Session 进行会话的应用程序。我可以发布登录信息,但没有看到响应中设置了 cookie。话虽如此,我在 Heroku 上工作了(FE + BE 在同一个仓库上)

我尝试了很多方法来使其正常工作,但都失败了。

我尝试过的一些解决方案是:

  • app.set('信任代理', 1)
  • 快速会话({代理:true})
  • 将 cookie 域设置为.example.com
  • 为我的域启用 CORS
  • 设置要使用的 Axios{ withCredentials: true }
  • proxy_set_header X-Forwarded-Proto https;

NGinx 配置

server {
        root /var/www/example-backend;
        server_name api.example.com;
        location / {
                proxy_pass http://127.0.0.1:3000;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Host $http_host;
                proxy_set_header X-NginX-Proxy true;
        }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
Run Code Online (Sandbox Code Playgroud)

相关快递代码

const express = require('express');
const bodyParser = require('body-parser');
const logger = require('morgan');
const passport = require('passport');
const session = require('express-session');
const cors = require('cors');

const app = express();

const routes = require('./routes');
const server = require('./db');

app.use(logger('dev'));
app.use(cors({
  origin: 'https://example-frontend.netlify.com',
  credentials: true,

}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.set('trust proxy', 1)
app.use(
  session({
    secret: 'secrets',
    resave: false,
    saveUninitialized: true,
    cookie: { domain: '.example-frontend.netlify.com' }
  })
);


app.use(passport.initialize());
app.use(passport.session());

const Account = require('./models/account');

passport.use(Account.createStrategy());
passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());

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

app.listen(3000, async () => {
  await server.start();
  console.log('HTTP running on port 3000');
});
Run Code Online (Sandbox Code Playgroud)

路线.js

const express = require('express');
const router = express.Router();

router.get('/testCookie', (req, res) => {
  res.cookie('test', 'hi', { domain: 'example-frontend.netlify.com'})
  res.sendStatus(200);
});

Run Code Online (Sandbox Code Playgroud)

相关前端代码

import axios from 'axios';

const API_ROOT =
  process.env.NODE_ENV === 'production'
    ? 'https://api.example.com/api'
    : '/api';
const API_RECIPE = `${API_ROOT}/recipe`;
const API_ACCOUNT = `${API_ROOT}/account`;

export const testCookie = () =>
  axios.get(`${API_ROOT}/testCookie`, { withCredentials: true });
Run Code Online (Sandbox Code Playgroud)

我认为这与跨域 cookie 有关,但我已经耗尽资源来了解可能出现的问题。我觉得这可能是在 nginx 级别,因为这是当前设置和 Heroku 设置之间唯一发生变化的部分。

任何帮助将不胜感激!


更新:我不确定哪个部分解决了这个问题,但这是我的工作代码

后端

app.use(
  require('express-session')({
    secret: process.env.MONGO_SESSIONS_SECRET,
    cookie: {
      maxAge: 1000 * 60 * 60 * 24 * 7,
      ...(process.env.NODE_ENV !== 'dev'
        ? { domain: '.example.com' }
        : {}),
    },
    store: store,
    resave: true,
    saveUninitialized: true,
  })
);

app.use((req, res, next) => {
  const acceptedOrigins = [
    'http://example.com',
    'http://beta.example.com',
    'http://localhost:3000',
  ];

  let [origin] = acceptedOrigins;

  if (acceptedOrigins.includes(req.headers.origin)) origin = req.headers.origin;

  res.set({
    'Access-Control-Allow-Origin': origin,
    'Access-Control-Allow-Credentials': true,
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Methods': 'DELETE',
  });

  if (process.env.NODE_ENV === 'dev' && req.method === 'OPTIONS') {
    return res.sendStatus(200);
  } else {
    next();
  }
});
Run Code Online (Sandbox Code Playgroud)

前端

import axios from 'axios';

axios.defaults.withCredentials = true;

let API_ROOT = 'http://127.0.0.1:3001/api';

export const Account = {
  endpoints: {
    login: `${API_ACCOUNT}/login`,
    resendVerification(id) {
      return `${API_ACCOUNT}/verify/resend?id=${id}`;
    },
  },
  login(body) {
    return axios.post(this.endpoints.login, body);
  },
  resendVerification(id) {
    return axios.get(this.endpoints.resendVerification(id), {
      withCredentials: true,
    });
  },
};
Run Code Online (Sandbox Code Playgroud)