首页 > 解决方案 > Express + Passport + Nginx - req.user 仅在实时环境中为空

问题描述

我有一个正在运行的应用程序,它使用 Passport 从我的 express API 获取用户数据以进行 Google OAuth2 识别。在实时环境request.user中返回为空,在开发中它正在工作。

router.get('/user', (request, response) => {
  response.send(request.user);
});

我已经在 Netlify 上部署了前端,在 Digital Ocean 的 nginx 服务器上部署了 express API。

我认为这与快速会议有关?我已经尝试了很多东西:

设置服务器选项如下:

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.set('trust proxy', 1);
app.use(function (request, response, next) {
  response.header('Access-Control-Allow-Credentials', true);
  response.header(
    'Access-Control-Allow-Origin',
    request.headers.origin,
  );
  response.header(
    'Access-Control-Allow-Methods',
    'GET,PUT,POST,DELETE',
  );
  response.header(
    'Access-Control-Allow-Headers',
    'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept',
  );
  if ('OPTIONS' == request.method) {
    response.send(200);
  } else {
    next();
  }
});
app.use(
  session({
    secret: process.env.COOKIE_SECRET,
    resave: false,
    saveUninitialized: true,
  }),
);
app.use(passport.initialize());
app.use(passport.session());

我的前端 fetch 调用包含credentials

const response = await fetch(`${API_PATH}auth/user`, {
    method: 'GET',
    credentials: 'include',
});

将此添加到 nginx 配置中:

location  / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
}

在我的本地环境中,请求标头包含一个 cookie,在实时环境中仅Set-Cookie在响应中设置。

请在此处找到完整的存储库:https ://github.com/sanderdebr/flightscanner

标签: node.jsexpressnginxfetchpassport.js

解决方案


编辑

我原来的想法是正确的。我找到了您的网站并且能够确认 Chrome 正在阻止 cookie。

Set-Cookie: connect.sid=s%....g37cv9X...sbdA; Domain=flightscanner.netlify.app; Path=/; HttpOnly; Secure

This set cookie was blocked because its domain attribute was invalid with regards to the current host url.

这将解决您的问题cookie: {httpOnly: false},但不是正确的方法。你需要弄清楚你的 NGINX 配置有什么问题并修复它。

原来的

您的要点的第 5 行显示Path=/; HttpOnly如果您通过 HTTPS 访问前端,那么 chrome 将忽略 Set-Cookie 标头。这暗示了您的 nginx 配置存在问题。您可以为测试做的一件事是:

app.use(
  session({
    secret: process.env.COOKIE_SECRET,
    resave: false,
    saveUninitialized: true,
    cookie: {httpOnly: false},
  }),
);

您需要确保并清除 Chrome 中的所有网站数据。

获取 Fiddler 4 并将其设置为代理 SSL/HTTPS 流量。这将帮助您正确获取标题并查看其他问题。此外,在 Chrome 开发工具中选择具有登录请求的条目。检查 Set-Cookie 的标题选项卡,然后检查 Cookies 选项卡。我的猜测是您会看到带有 HttpOnly 的 Set-Cookie 并且 cookie 已存储,但 chome 不会将其与您的 fetch 请求一起发送。

为了提供一些额外的帮助,我将从生产中添加部分 nginx 配置。我不能发布完整的配置,对不起。请记住,我们使用 Docker,因此某些部分将不适用,需要进行更改以匹配您的环境。

这是我们的 express/passport 会话配置:

    expressSession: {
        name: EXPRESS_SESSION_NAME,
        secret: EXPRESS_SESSION_SECRET,
        resave: false,
        saveUninitialized: false,
        proxy: true,
        rolling: true,
        cookie: {
            maxAge: 15 * MINUTE,
            sameSite: 'none',
            secure: !isDev,
            httpOnly: !isDev,
            domain: EXPRESS_SESSION_COOKIE_DOMAIN,
        }
    },
    passportSession: {
        httpOnly: !isDev,
    }

我们运行多个子域,其中一个是我们的 Portal 一个内部客户 React/Express 应用程序。express 部分在 docker 容器中运行。为了处理这个问题,我们需要在每个子域基础上处理 CORS。我们使用映射是因为 IF 真的很邪恶!看:https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/

# setup out mappings in HTTP block outside SERVER blocks
# Used to disabled logging
map $request_uri $loggable {
    /healthcheck 0;
    default 1;
}
# allow all subdomains - each subdomain will get their own server block
map $http_origin $cors_origin {
    default "";
    "~^https://(localhost|.*\.ourdomain.network)$" "$http_origin";
}

# Websockets
map $http_upgrade $connection_upgrade {
    default upgrade;
    ""      close;
}

基本服务器块在这里。我们使用 NGINX 来卸载 SSL/HTTP2。我们前面有一个 AWS NLB,但无法使用 ALB,因为我们有一些使用 SSL 证书进行身份验证的用户。此配置中未显示。

# This is a server block for a subdomain
server {
    listen      443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name     portal.ourdomain.network;
    allow           all;
    access_log      /dev/stdout proxy_log;
    error_log       stderr      debug;
    set             $handler    "https.portal.ourdomain.network";

    # prevent Cross-site scripting (XSS) by enabling built in browser filter
    # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
    add_header X-XSS-Protection "1; mode=block";

    # enable HSTS (HTTP Strict Transport Security) to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
    # https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
    add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload" always;

    include sites/star.ourdomain.network/portal/*.nginx;
}

这是您最应该关注的部分。在这里,我们有一个用于处理 API 请求的位置,即流向我们的 express 应用程序的流量。我们让 NGINX 处理预检请求,因为它更容易且不易出错。其中一些标题超出了应有的范围。我们将在下个月左右进行测试,将它们缩减到所需的最低限度。

# API Location Block
location /api {
    set                     $handler "$http_origin/api";
    access_log              /dev/stdout proxy_log;
    error_log               stderr debug;
    resolver                127.0.0.11;         # Docker DNS
    proxy_redirect          off;

    add_header Access-Control-Allow-Origin      $cors_origin;
    add_header Vary                             Origin;
    add_header Access-Control-Allow-Credentials true;
    # Always allowed Accept, Accept-Language, Content-Language, Content-Type
    add_header Access-Control-Allow-Headers     "Origin,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
    add_header Access-Control-Allow-Methods     "OPTIONS,GET,PUT,POST,DELETE";

    if ($request_method = 'OPTIONS') {
        set $handler "OPTIONS:portal.ourdomain.network:443/api";
        ## DOMAIN/SUBDOMAIM CORS Preflight
        add_header 'Access-Control-Allow-Origin'    $cors_origin;
        add_header Vary                             Origin;
        add_header 'Access-Control-Allow-Methods'   'OPTIONS,GET,PUT,POST,DELETE';
        # Custom headers and headers various browsers *should* be OK with but aren't
        add_header 'Access-Control-Allow-Headers'   'Origin,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        # Tell client that this pre-flight info is valid for 20 days
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        add_header Sec-Fetch-Site same-site;
        return 204;
    }

    proxy_set_header        Host                  $host;
    proxy_set_header        X-Real-IP             $remote_addr;
    proxy_set_header        X-Scheme              $scheme;
    proxy_set_header        X-Forwarded-For       $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Host      $server_name;
    proxy_set_header        X-Forwarded-Proto     https;
    proxy_set_header        Upgrade               $http_upgrade;
    proxy_set_header        Connection            $connection_upgrade;
    proxy_connect_timeout   60;
    proxy_read_timeout      60;

    # We have to do it this way otherwise NGINX cannot resolve the container and will fail to start
    set $proxy_host         "server:4000";
    proxy_pass              http://$proxy_host;
}

这是服务器块设置前端版本的包含文件。我们正在考虑使此标头基于默认值。

set $s3Bucket       "portal.ourdomain.network.s3-website-us-gov-west-1.amazonaws.com";
set $version        "0.1.28";

这只是将前端流量代理到 AWS S3 存储桶的基本位置。

# Front End Location
location / {
    set $handler            "$http_origin/";
    access_log              /dev/stdout proxy_log;
    error_log               stderr debug;
    ssi on;
    resolver                8.8.8.8;
    proxy_intercept_errors  on;
    proxy_redirect          off;
    proxy_set_header        Host $s3Bucket;
    proxy_hide_header       x-amz-id-2;
    proxy_hide_header       x-amz-request-id;

    add_header Access-Control-Allow-Origin      $cors_origin;
    add_header Vary                             Origin;
    add_header Access-Control-Allow-Headers     *;
    add_header Access-Control-Allow-Methods     'GET, OPTIONS';
    proxy_set_header        X-Real-IP             $remote_addr;
    proxy_set_header        X-Scheme              $scheme;
    proxy_set_header        X-Forwarded-For       $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto     https;
    proxy_connect_timeout   60;
    proxy_read_timeout      60;

    rewrite                 ^/(.*)$ /$version/$1 break;
    proxy_pass              http://$s3Bucket;
}

推荐阅读