How to setup Node to make different domains share cookies? - node.js

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'
}
);

Related

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

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,});

express-session cookie still exist despite logout

I have this logout route with expressJS using express-session :
router.post('/logout', (req, res) => {
req.session.user = null;
req.session.destroy((err) => {
if (err) {
return res.status(400).end();
} else {
return res.status(200).end();
}
});
});
Although the user is logged out Correctly and the sid changes, The cookie still exists!! which freaking me out.
I want to completely remove the cookie to calm my heart.
This is the config of the express-session package
app.use(
session({
store: new MariaDBStore({
pool: require('./config/db_pool')
}),
name: 'sid',
secret: process.env.KEY,
saveUninitialized: false,
resave: false,
cookie: {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'development' ? false : true
}
})
);
I git the answer from #Joe comment above and this like
Answer from here
using this close completely removes the cookie.
the options of res.clearCookie are not optional .
res.clearCookie('sid', { path: '/' });

How can I set the domain attribute in express-session based on the request origin?

I'm using express session. I set the domain domain: 'mydomain.com' so that the session cookie can be set between subdomains- like api.mydomain.com and staging.mydomain.com.
But this prevents the Set-Cookie header from setting the cookie when testing with a localhost frontend. I get Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url.
So I need to make the domain attribute change to localhost if the origin is localhost.
If I conditionally set the domain, we don't have access to req:
app.use(session({
secret: 'very secret 12345',
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
cookie: {
domain:
req.get('origin').slice(0, 17) === 'http://localhost:' ? 'localhost' : 'mydomain.com',
secure: true,
httpOnly: true,
sameSite: none,
},
})
);
This returns ReferenceError: req is not defined.
So I tried calling session in a custom middleware to get access to req:
app.use((req, res, next) =>
session({
secret: 'very secret 12345',
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
cookie: {
domain:
req.get('origin').slice(0, 17) === 'http://localhost:' ? 'localhost' : 'mydomain.com',
secure: true,
httpOnly: true,
sameSite: none,
},
})
);
But it doesn't work. It seems that with this, res, req, and next don't get passed in to the middleware function that session() returns. I also trying calling the function session() that returned -session({..options..})() , but that didn't work either.
How can I set the domain attribute based on the request origin?
I had to call the function and pass in req, res, and next
app.use((req, res, next) =>
session({
secret: 'very secret 12345', // to do, make environment variable for production
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
cookie: {
domain:
req.get('origin').slice(0, 17) === 'http://localhost:' ? 'localhost' : 'mydomain.com',
secure: true,
httpOnly: true,
sameSite: none,
},
},
})(req, res, next)
);

Localhost react app not receiving cookie from Heroku hosted node API

I am able to log in and out with POSTMAN through the heroku hosted node API.
In my react application, the API call with AXIOS (withcredentials:true) does not set the passport cookies, causing the session to not persist. Localhost react and Localhost server does not have this problem.
HEROKU SERVER SIDE, I have the following code:
app.use(cors({
origin: "http://localhost:3000",
credentials: true
}));
app.enable('trust proxy');
mongoose.connect(dbconfig.url, {
useUnifiedTopology: true,
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false });
// Parse URL-encoded bodies (as sent by HTML forms)
app.use(express.urlencoded({extended: true}));
app.use(cookieParser('street'));
app.use(session({
secret: 'katsura street',
resave: true,
saveUninitialized: true,
proxy: true,
store: new MongoStore({ mongooseConnection: mongoose.connection },)
}));
app.use(passport.initialize());
app.use(passport.session());
I have checked that the cookie parser is above the session,
the session is initialized before passport.
Is my cors affecting my local reactapp? instead of localhost, am I supposed to reference my local computer's external API?
REACT SIDE:
Axios.post(Config.ServerURL + 'auth/login',
{email: this.state.email, password: this.state.password},
{
withCredentials: true
}).then(result => {
console.log(result.data);
}).catch(error => {
//make sure message doesn't crash
if (error.response !== undefined) {
try {
console.log(error.response);
this.setState({message: error.response.data.info.message})
}catch(error) {
console.log(error);
}
}
});
After checking the headers and seeing that the cookies are sent but filtered out, I came to a article: heroku blog
After 2019, Chrome and other browsers have cross domain cookies disabled to prevent CSRF attacks.
In my use case, I can just turn it off with the following on the Node API server:
app.use(session({
secret: 'street',
resave: true,
saveUninitialized: true,
proxy: true,
cookie: {
sameSite:'none',
secure:true
},
store: new MongoStore({ mongooseConnection: mongoose.connection },)
}));
changing samesite to none, will allow chrome to set your cookies with cross domains.
In my case, it was just setting, in the session settings, cookie.sameSite to "none", and proxy to true.

Express-Session is undefined

I am new to node.js and angular4 and am working on an application which required user authentication. I want to save the user role in a session in node.js and want to access that session in multiple routes. However, I am unable to get the role in other routes. I have checked many answers regarding the same but none have worked for me so far.
Scenario:
I have implemented Express-Session in app.js
const session = require('express-session');
const cookieParser = require('cookie-parser')
const app = express();
const port = 4000;
app.use(cors());
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.all('*',function(req, res, next){
res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header("Access-Control-Allow-Credentials", "true");
next();
});
app.use(cookieParser())
app.use(session({secret: "my secret", resave: false, saveUninitialized: true, cookie: { secure: !true }}));
I have a file named user.js which is being used for login user. After successful verification, user information is stored in a session.
res.json({
success: true,
token: 'JWT ' + token,
user: {
id: user[0].UserId,
FirstName: user[0].FirstName,
MiddleName: user[0].MiddleName,
LastName: user[0].LastName,
EmailID: user[0].EmailID,
PhoneNo: user[0].PhoneNo,
Role: user[0].Role,
DistrictId: user[0].DistrictId
},
});
req.session.userRole = user[0].Role;
req.session.save();
At this point, req.session.userRole has the user role.
However, when I use req.session.userRole in another route, eg: dept.js, it shows undefined.
Also, when I use Chrome app: Postman, it displays
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false },
userRole: 'ADMN' }
But when I run the application which is using Angular4, it just shows
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false }
PS: Node.js app is on port: 4000 and angular4 app is on 4200.
Thanks in Advance.

Resources