Edit
My original thinking was correct. I found your site and was able to confirm Chrome is blocking the 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.
This will resolve your issues cookie: {httpOnly: false} but is not the correct way to do it. You will need to figure out what is wrong with your NGINX config and fix that.
Original
Line 5 of your gist shows Path=/; HttpOnly If you hitting you frontend over HTTPS then chrome is going to ignore the Set-Cookie header. This hints at an issue with your nginx config. One thing you can do FOR TESTING ONLY is:
app.use(
session({
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: true,
cookie: {httpOnly: false},
}),
);
You'll need to make sure and clear any site data in Chrome.
Get Fiddler 4 and set it up to proxy SSL/HTTPS traffic. This will help you with getting headers right and seeing other issues. Additionally, in Chrome DEV tools select the entry that has the login request. Check the header tab for the Set-Cookie and then the Cookies tab. My guess is you will see the Set-Cookie with the HttpOnly and the cookie is store but chome wont send it with your fetch request.
To provide some additional help I'm going to add in part of our nginx config from production. I can't post the full config, sorry. Keep in mind we use Docker so some parts wont apply and will need to be changed to match your environment.
Here is our express/passport session configs:
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,
}
We run multiple subdomains one of which is our Portal a in house customer React/Express app. The express part is running in a docker container. To handle this we need to handle CORS on a per subdomain bases. We use mapping because IF is truly evil! see: 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;
}
Basic server block here. We use NGINX to off load SSL/HTTP2. We have an AWS NLB in front but could not uses ALB as we have some uses who auth with an SSL cert. Not shown in this config.
# 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;
}
This is the part you should pay closest attention to. Here we have a location for handling API request IE the traffic going to our express app. We have NGINX handle the preflight request as it's just easier and less error prone. Some of these headers have more than they should. We will be testing over the next month or so trimming them down to the bare minimum needed.
# 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;
}
This is an include file for the server block to set the front end version. We looking at making this header based with a default.
set $s3Bucket "portal.ourdomain.network.s3-website-us-gov-west-1.amazonaws.com";
set $version "0.1.28";
This is just a basic location to proxy the front end traffic to an AWS S3 bucket.
# 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;
}