Nest.js express-session Set-Cookie header is not working - CORS issue - nestjs

I've got the frontend and backend on different servers. when I first deployed the services I've got "SameSite: none" because of different origins, then when I set it to none, it required me to set "Secure: true" as well, after setting that I'm unable to see the Set-cookie header on server's response and on production the cookie is just not recieved.
here's main.ts with the express-session middleware:
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const sessionSecret = app.get(AppConfigService).getConfig().session.secret;
const frontDomain = app.get(AppConfigService).getConfig().front.domain;
const port = app.get(AppConfigService).getConfig().app.port;
app.setGlobalPrefix('api');
app.use(
session({
name: 's.id',
secret: sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 360000, // 1hour in seconds
secure: process.env.NODE_ENV !== 'production' ? false : true,
},
store: new PrismaSessionStore(new PrismaClient(), {
checkPeriod: 2 * 60 * 1000, //ms
dbRecordIdIsSessionId: true,
dbRecordIdFunction: undefined,
}),
}),
);
app.enableCors({
origin: [frontDomain, `${frontDomain}/`],
credentials: true,
});
app.use(passport.initialize());
app.use(passport.session());
await app.listen(port);
}
bootstrap();

try this, it works for me
const app = await NestFactory.create<NestExpressApplication>(AppModule{cors: true,});

Related

Why is the express session cookie not stored in the browser's cookie storage on production but is working on localhost/development? (express + react)

I have created a test app, my react app is deployed at vercel and my node express is deployed at render.com. I set the same domain on both to solve cross-site cookie problems (app.mydomain.online)(api.mydomain.online). Now no error is showing when I view the cookie in the header but still when I check the cookie storage it is still not stored or not being saved at the browser's cookie storage.
server is created via npm init.
react is created via npm create-react-app.
as of now this is my sample code.
server
const express = require("express");
const cors = require("cors");
const session = require('express-session');
const app = express();
require('dotenv').config();
const PORT = process.env.PORT;
app.use(express.json());
app.use(cors({
origin: 'https://app.myDomain.online',
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true
}));
const oneDay = 1000 * 60 * 60 * 24;
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: 'none' }
}));
app.get('/createSession', (req, res) => {
req.session.user = 'user';
res.send('new session is created');
});
app.get('/', (req, res) => {
res.send('get sess')
});
app.get('/getSession', (req, res) => {
if(req.session.user){
res.send('active');
}else{
res.send('not active');
}
});
app.listen(PORT, () => {
console.log(`The server is running on port ${PORT}`);
});
react
import React from 'react'
import { useEffect } from 'react';
import Axios from 'axios';
function Test() {
useEffect(() => {
Axios.get(' https://api.myDomain.online/createSession',
{ withCredentials: true }
);
}, []);
return (
<div>Test</div>
)
}
export default Test;
From the documentation for express-session...
cookie.expires
Specifies the Date object to be the value for the Expires Set-Cookie attribute. By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application.
The docs go on to prefer the maxAge property to control this. Choose a time frame that makes sense for your application. For example, 1 week...
app.use(
session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: "none", maxAge: 7 * 24 * 60 * 60 * 1000 },
})
);
I have already fixed it a few days ago, I found out that the reason why the cookie was blocked was that the cookie has exactly the same domain as the server or probably has the same link address. As the domain of the server is api.myDomian.online, the cookie domain can't be api.myDomain.online. Not sure if that is the direct reason, but somewhat similar I think as the code works by setting a new domain to the cookie. I just removed the subdomain of the cookie like .myDomain.online and it works. here is my configuration. My backend is already deployed at aws when I test it but it could also work in render. I will try it out later on.
This is my new configuration
const oneDay = 1000 * 60 * 60 * 24;
const APP_SESSION = session({
secret: 'secrete',
resave: false,
saveUninitialized: true,
name: 'session',
cookie: {
secure: true,
sameSite: 'none'
maxAge: oneDay,
domain: '.domain.com'
}
});
Haven't tried to remove sameSite if it will still work.

How to make express-session work on a cross-site setup (Cannot set a cookie from the backend to the frontend)?

How to make express-session work on a cross-site setup (Cannot set a cookie from the backend to the frontend)?
I created a test app as I am having a hard time finding ways to set cookies to the front end. When I try to set sameSite: 'none' and cookie: true, I cannot find the cookie in the header. But when I set the cookie to false and remove the sameSite, The cookie was in the header but it was blocked and i am getting an error such as I should set the sameSite=none and secure to true. The website is deployed at render.com.
The link of the react looks like this
https://react-websiteName-2d0w.onrender.com
The link of the server looks like this
https://api-websiteName-hj7g.onrender.com
//server
const express = require("express");
const cors = require("cors");
const sessions = require('express-session');
const app = express();
const PORT = 3001;
app.use(express.json());
app.use(cors({
origin: 'https://react-websiteName-2d0w.onrender.com',
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true
}));
const oneDay = 1000 * 60 * 60 * 24;
app.use(sessions({
secret: "secret",
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true,
maxAge: oneDay,
sameSite:'none'
},
resave: false
}));
app.post('/createSession', (req, res) => {
req.session.user = req.body.user;
res.send('new session is created');
});
app.listen(PORT, () => {
console.log(`The server is running on port ${PORT}`);
});
//react app
import { useEffect } from 'react';
import Axios from 'axios';
import './App.css';
import axios from 'axios';
const App = () => {
axios.defaults.withCredentials = true;
useEffect(() => {
Axios.post('https://api-websiteName-hj7g.onrender.com/createSession',
{ user: "user" }
);
}, []);
return (
<div>
<h1>Check if there is a cookie</h1>
</div>
);
}
export default App;
I am trying to make express-session work on my project when deployed with different domian.
Are there better alternatives or methods that could work in render.com instead of express-session?

How to setup Node to make different domains share cookies?

I have the problem that on production (Apache server/MERN stack) certain cookies are not accessible from the browser with document.cookie.
On localhost, both front-end and back-end are on the same domain, namely localhost, and just use different port numbers. Because they are working on the same domain, they share cookies.
On production, however, front-end and back-end operate on different (sub)domains. As a result, they don't have access to each other's cookies.
How can I make the cookies set by the back-end accessible for the front-end, also on production?
I thought this should be done with CORS, and with httpOnly: false and sameSite: 'none'. But the Node setup below doesn't work. In the browser I'm unable to access from the front-end the cookies set by the back-end.
var cors = require("cors");
const session = require("express-session");
const csurf = require("csurf");
const cookieParser = require("cookie-parser");
var corsOptions = {
origin: process.env.CORS_ORIGIN_URL.split(","), // the front-end is included here.
credentials: true,
exposedHeaders: ["set-cookie"],
};
app.use(cors(corsOptions));
let sessionSettings = {
secret: process.env.SESSION_SECRET,
key: process.env.SESSION_KEY,
store: sessionStore,
resave: true,
saveUninitialized: true,
cookie: {
secure: false,
},
};
app.use(session(sessionSettings));
const protect = csurf({
cookie: true,
httpOnly: false,
sameSite: 'none'
});
app.use(
cookieParser("test", {
sameSite: 'none',
httpOnly: false,
secure: false,
maxAge: 900000,
})
);
app.use((req, res, next) => {
protect(req, res, next);
});
app.use((req, res, next) => {
if (req.csrfToken) {
res.cookie(
"XSRF-TOKEN",
req.csrfToken(),
{
secure: true,
httpOnly: false,
sameSite: 'None'
}
);
}
next();
});
app.use(`${process.env.API_PATH}/csrf`, (req, res) => {
return res.status(200).json({
status: true,
csrfToken: req.csrfToken(),
});
});
...
Here you need to share the cookie with subdomains and main domain. You can do this by adding a domain field in res.cookie options. Now your main domain and subdomains can access this cookie.
res.cookie(
"XSRF-TOKEN",
req.csrfToken(),
{
secure: true,
httpOnly: false,
sameSite: 'None',
domain: 'mydomain.com'
}
);

Receive Express session Id from React app

I am learning Express.js, trying to create an api and connect it to React frontend. In express, I'm using express-session to create a session. For authentication, I use passport.js.
Here is the part of my app.js file:
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/DB_NAME',
collection: 'UserSessions'
});
const app = express();
app.use(cors())
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
store,
cookie: {
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7,
httpOnly: true
}
}
app.use(session(sessionConfig));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser())
app.listen(8080, () => {
console.log('listening on port 8080')
});
My issue is that when I send a request to my backend from my React app, I don't receive session id as a cookie. On my frontend, I use axios, to send requests:
import axios from 'axios';
export default axios.create({
baseURL: "http://localhost:8080/",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
Why I don't receive a cookie from backend on my frontend and how can I fix this?
Thank you!
Because your cookie is a httpOnly cookie so in order to send the cookie you can do by fetch method:
const req = await fetch(URL,{
method : "POST",
credentials : "include", // to send HTTP only cookies
headers: {
"Contetnt-Type" : "application/json"
},
body : JSON.stringigy({name : "Bob"})
}):
const result = await req.json();
By axios you can also add withCredential properties:
axios.get(BASE_URL + '/todos', { withCredentials: true });
and also in backend consider this parametrs:
const corsOptions = {
optionsSuccessStatus: 200,
credentials: true,
}
app.use(cors(corsOptions))

My cookie is not set in Browser of studio.apollographql explorer

i am trying to set cookie on apollo Studio explorer, my redis setup successfully store the cookie, but nothing is set on the browser.
Please What am i doing wrong ?
when make a monitoring of redis with redis-cli ,i can see that the token is receive.
i am using : apollo-server-express "^3.3.0", and express-session "^1.17.2"
async function startServer() {
const app = express();
const httpServer = http.createServer(app);
const RedisStore = connectRedis(session);
const redisClient = redis.createClient({
host: "127.0.0.1",
port: 6379,
});
app.use(
session({
name: "pid",
store: new RedisStore({
client: redisClient,
}),
cookie: {
maxAge: 1000 * 60 * 10,
httpOnly: false,
secure: true,
sameSite: "none",
},
saveUninitialized: false,
secret: "EOJ7OmvIAhb2yJpCI947juj6F8CppHCp",
resave: false,
})
);
const server = new ApolloServer({
schema,
context: createContext,
formatError: (error) => {
return {
message: error.message,
};
},
});
await server.start();
server.applyMiddleware({
app,
cors: { credentials: true, origin: "https://studio.apollographql.com" },
});
await new Promise((resolve: any) =>
httpServer.listen({ port: process.env.PORT }, resolve)
);
}
startServer().catch((err) => console.log(err));

Resources