I am in the middle of decoupling my frontend from my backend repo. In doing so I am hosting my server on a personal Raspberry Pi 3 on my home network. My frontend is hosted by Netlify. I have run into a problem where I am not able to set cookies on my client with Express.Js although I am able to see the cookie set in Postman.
My current set up is as follows:
FE (Netlify - example.com) -> Nginx (Reverse Proxy - api.example.com) -> Node.js (Express - listening for http)
I am unable to log into my application which uses Express-Session for sessions. I can post login information but do not see a cookie being set on the response. Saying that, I have this working on Heroku (FE + BE on same repo)
I have attempted quite a few things to get this working but all have failed.
Some solutions I have tried are:
- app.set('trust proxy', 1)
- express-session({ proxy: true })
- set cookie domain to be
.example.com
- enabled CORS for my domain
- set Axios to use
{ withCredentials: true }
- set
proxy_set_header X-Forwarded-Proto https;
NGinx Config
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
}
Relevant Express Code
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');
});
routes.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);
});
Relevant Frontend Code
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 });
I assume this has to do with cross-origin cookies but I have run out of resources to read up on what the possible problem could be. I feel like this might be at the nginx level as that is the only piece that has changed between current setup and Heroku setup.
Any help would be greatly appreciated!
UPDATE: Im not sure what portion solved this problem but here is my working code
backend
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();
}
});
frontend
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,
});
},
};