Can't make lusca CSRF work with https: 403 forbidden - node.js

This is driving me nuts. I have tried reading the lusca source code but found it hard to understand.
Checked several examples too, but since each config is different, and the only debugging output I have are two strings to compare, I'd better ask for some help!
Here's the code server side:
app.use([
cookieParser(process.env.SESSION_SECRET),
session({
resave: false,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
store: new MongoStore({ url: MONGO_URL, autoReconnect: true }),
cookie: {
secure: process.env.NODE_ENV === 'production'
},
}), lusca({
csrf: true,
xframe: 'SAMEORIGIN',
xssProtection: true,
})]);
And from the clientside, I send Ajax POST requests with the x-csrf-token:l0gH3xmssge53E/p2NsJ4dGnHaSLdPeZ+bEWs= header in it:
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'x-csrf-token': CSRF_TOKEN
}
});
Crazy thing is, it's working locally, but as soon as I go https in production, I get the 403 Forbidden error message.
Here are the versions I use:
"cookie-parser": "1.4.3",
"express-session": "1.15.3",
"lusca": "1.5.1",
Also I read this from the express/session doc:
Note Since version 1.5.0, the cookie-parser middleware no longer needs to be used for this module to work.
But as far as I'm concerned, I need to store some persistent ID of the users (longer than the session). I need to use cookies for that, right?
I'd like to understand better on the whole session/cookie thing, but until now I never found any useful resource on the topic.
Thanks!

If you are running your Node.js server behind a proxy you will need to set trust proxy to true:
var isProductionEnv = process.env.NODE_ENV === 'production';
app.use([
cookieParser(process.env.SESSION_SECRET),
session({
resave: false,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
store: new MongoStore({ url: MONGO_URL, autoReconnect: true }),
proxy: isProductionEnv,
cookie: {
secure:isPrudictionEnv,
},
}), lusca({
csrf: true,
xframe: 'SAMEORIGIN',
xssProtection: true,
})]);
app.set('trust proxy', isProductionEnv);
Check out this stack overflow answer. Also check out this page on Express behind proxies.

Related

Express doesn't set-cookie when SameSite is none

I have read a lot of other similar questions, but I couldn't solve the issue.
My setup is Node + Express + PassportJs and everything works in development, but I have problems on production.
With the following code, I see that the session cookie is sent back in the response, but I also get a message saying that it won't be applied as SameSite is lax (the default) and the response comes from another site (frontend and backend do not have the same origin).
app.use(
session({
secret: "foo",
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGO_DB_CONN_STRING! }),
cookie: { httpOnly: true }
})
);
So I changed it to this, so to specify SameSite and Secure in production, but at this point, no cookie is set anymore!
app.use(
session({
secret: "foo",
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGO_DB_CONN_STRING! }),
cookie: isProduction ? { httpOnly: true, sameSite: "none", secure: true } : {} // <-- only change
})
);
What could be the cause? I've tried to fix it by playing with CORS (no success) and other 100 things. Yet it seems some quirk I am missing.
depending on what service you use to deploy your API(netlify, render.com, heroku other...) you have to enable proxy
this.app.enable('trust proxy');
it fixed my issue

Why can't I set cookies over HTTPS?

I have a server that has its own domain and is using HTTPS. I have a website that has its own domain and is using HTTPS as well.
On the home page of the website, there is a login button and a sign up button. Both buttons lead to forms to execute their respective tasks, and both forms send requests to the server that respond with cookies, or at least that's what they are supposed to do.
I am using express-session in combination with Redis to store the session ids. Here is the config for that (connectToRedis is simply a function that returns a RedisClient):
const RedisStore = connectRedis(session);
const redisClient = await connectToRedis();
app.use(
session({
store: new RedisStore({
client: redisClient,
}),
cookie: {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: TEN_YEARS,
},
resave: false,
saveUninitialized: false,
secret: SECRET,
name: AUTH_COOKIE,
})
);
For some reason the cookies are not being sent in the requests. The Set-Cookie header isn't even showing up! I tried changing SameSite to none (article), but that didn't work.
This is my first time deploying a production website, so all this HTTPS stuff is kind of new to me.
Thanks for your help, and if you need any extra information, please let me know.
IMPORTANT UPDATE:
Finally, I have made some progress (all it took was a pizza break).
I added the path key to my cookie config and gave it the value of /. I also set proxy to true.
So now it looks like this:
const RedisStore = connectRedis(session);
const redisClient = await connectToRedis();
app.use(
session({
store: new RedisStore({
client: redisClient,
}),
cookie: {
httpOnly: true,
secure: true,
sameSite: "none",
maxAge: TEN_YEARS,
path: "/",
},
resave: false,
saveUninitialized: false,
secret: SECRET,
name: AUTH_COOKIE,
proxy: true,
})
);
With this, the cookie is finally appearing in the requests, but it isn't being set in the browser...

Node.js, Angular, express-session: Chrome 80 does not save session because of cookie policy (sameSite cookies)

I have a Node.js, Angular app. (Node.js server written in TypeScript).
Node.js Server is running on an Amazon EC-2 instance, the Angular client is on another server.
For the login session, I use express-session. I am not using cookies in the app, so I think the problem is with the express-session cookies.
On Firefox it works properly, but with Google Chrome (80.0.3987.149) it not works: Chrome doesn't save the session (so I can not leave the login page) and warns:
A cookie associated with a cross-site resource at http://addressof.myserverapp.com/ was set without the SameSite attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
In Node.js server I set the express-sessions this way:
import cookieParser from 'cookie-parser';
app.use(cookieParser(secret));
app.use(session({
secret: secret,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: false,
maxAge: null,
secure: true,
sameSite: 'none'
},
store: sessionStore // MySqlStore - express-mysql-session
}));
I also tried to solve the problem with this code snippet (from https://github.com/expressjs/session/issues/725#issuecomment-605922223)
Object.defineProperty(session.Cookie.prototype, 'sameSite', {
// sameSite cannot be set to `None` if cookie is not marked secure
get() {
return this._sameSite === 'none' && !this.secure ? 'lax' : this._sameSite;
},
set(value) {
this._sameSite = value;
}
});
Server npm packages:
"cors": "^2.8.5",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"express-mysql-session": "^2.1.3",
"express-session": "^1.17.0",
Npm version: 6.13.4
Node version: 12.16.1
I spent days with this problem to figure out what am I doing wrong...
The issue here is that you are accessing the Website via HTTP, but the cookie secure setting is true, which means that it will only be sent via HTTPS.
If you set secure: false in the express-session cookie options, it will work:
import cookieParser from 'cookie-parser';
app.use(cookieParser(secret));
app.use(session({
secret: secret,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: false,
maxAge: null,
// allow the cookie to be sent via HTTP ("true" means "HTTPS only)
secure: false,
sameSite: 'none'
},
store: sessionStore
}));

Read express session cookie from React js app

I'm building a project with authentication. I'm using Node+React. I set an express session cookie on the back-end and I want a component in react to read that cookie to see if the user is authenticated or not. For some reason I can not access that cookie from the react(client-side)... Maybe someone could help out?
BACK:
app.use(session({
name: process.env.SESS_NAME,
resave: false,
saveUninitialized: false,
secret: process.env.SESS_SECRET,
cookie: {
maxAge: parseInt(process.env.SESS_LIFETIME),
sameSite: true, //strict,
secure: process.env.NODE_ENV === "production"
}
}))
FRONT:
import Cookies from "js-cookie";
...
console.log("cookie", Cookies.get("sid"));
I have a cookie named "sid" in this case and I can see it in my console in the browser... but when I try to access it its undefiend
thanks!
Your issue is that you have not set the httpOnly property on the cookie when configuring session. The default value is true which will prevent client browsers from reading the cookie.
Note be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie.
app.use(session({
name: process.env.SESS_NAME,
resave: false,
saveUninitialized: false,
secret: process.env.SESS_SECRET,
cookie: {
maxAge: parseInt(process.env.SESS_LIFETIME),
sameSite: false, // this may need to be false is you are accessing from another React app
httpOnly: false, // this must be false if you want to access the cookie
secure: process.env.NODE_ENV === "production"
}
}))
See the cookie options in docs

express-session - req.session not showing saved session

I am not able to view req.session saved data after storing userId after user logs in.
When I console.log the session from the login function I get the proper data...
Session {
cookie:
{ path: '/',
_expires: 2019-02-23T12:17:24.134Z,
originalMaxAge: 7200000,
httpOnly: true,
sameSite: true,
secure: false },
userId: 4 }
But when I console.log(req.session) from other routes after that I get a new blank session
Session {
cookie:
{ path: '/',
_expires: 2019-02-23T12:12:47.282Z,
originalMaxAge: 7200000,
httpOnly: true,
sameSite: false,
secure: false } }
I am working on my localhost using React frontend on port 3000 and Node/Express with express-session and redis-connect. When I view my Redis I see the stored session properly.
This is my session code...
app.use(
session({
store,
name: 'sid',
saveUninitialized: false,
resave: false,
secret: 'secret',
cookie: {
maxAge: 1000 * 60 * 60 * 2,
sameSite: true,
secure: false
}
})
)
I have tried all different values for these options and nothing works. Please help!
The answer came from a mix of answers from RobertB4 https://github.com/expressjs/session/issues/374
Apparently this problem is common when using CORS middleware and fetch api. In all of your fetch calls you need to send a credentials property set to 'include' and in your cors middleware you need to set an option to credentials: true. It is not good enough to do one or the other, you must do both...
fetch('url', {
credentials: 'include'
})
const corsOptions = {
credentials: true
}

Resources