I am using the express-session module, it works perfectly on localhost but on my website (hosted on Heroku using Cloudflare), the express session is being blocked as being a third party cookie. Here is the configuration for my session:
app.use(session({
resave: false,
saveUninitialized: false,
proxy : true,
cookie: {
maxAge: 3600000000000,
httpOnly: false,
secure: false,
domain: '.mydomain.com',
path: '/'
},
store: sessionStore,
secret: 'mysecret',
unset: 'destroy'
}));
Is this an issue with Express or maybe Cloudflare/Heroku?
#Why the cookie is blocked
From whatis.techtarget.com:
A third-party cookie is one that is placed on a user’s hard disk by a
Web site from a domain other than the one a user is visiting.
As you mentioned in your comment, your client and your server are on different domains:
www.castcrunch.com is my client side server's URL and cast-crunch-server.herokuapp.com is my backend server URL
You can read more about cookie domains in the RFC 6265:
The Domain attribute specifies those hosts to which the cookie will be sent.
#What you could do about that
As mentioned in this dzone article, you could use Json Web Tokens to do the authentication. Your server would send the token in the login response body, the client would store it and send it to the server in every subsequent request header.
The drawback with this approach, since you are storing the token, is that you would become vulnerable to XSS attacks. You have to pay special attention to that: sanitise all inputs, or better yet, use frameworks and languages that already to that.
Note: Of course, you could also uncheck the "block 3rd party cookies" option in the browser settings, but this does not seem like a long term solution :).
Related
I am using express-session to handle user sessions. The problem is when I'm on incognito mode in production it doesn't work. I use heroku to deploy my node js application.
On localhost I use this and it works on both normal and incognito mode.
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: store,
cookie: {
maxAge: 1000 * 60 * 60 * 100,
},
})
);
But once deployed, I use the code below since the top doesn't work on production and doesn't set the cookie. Now this works and sets the cookie but not on incognito mode.
app.use( session({
store: store,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
proxy: true,
cookie: {
secure: true,
maxAge: 1000 * 60 * 60 * 48,
sameSite: "none",
}, }) );
I found a similar problem but has no answers:
Express Session is not working in production on incognito mode but everything working fine locally
EDIT
After hours of searching. I think this problem has something to do with how heroku handles cookies if sent from a different from a different domain eg: www.my-front-end.netlify.app requests api from www.my-heroku-backend.app If someone on incognito allows third party cookies, then the problem will be solved. I may be wrong please feel free to correct me. Thanks everyone. For more info check:
https://devcenter.heroku.com/articles/cookies-and-herokuapp-com
TLDR: Heroku sends third party cookies. Incognito blocks third party cookies thus the error is created.
In my understanding, express session will be like a map stored inside the express instance running context..
Every request will be validated against the existence of an entry in that map..
In local you are running a single instance of express server...
That may not be the case in heroku instance.. there should be multiple instances of your express server running behind the proxy.
So.. your session creation request posted to instance_1 and it responded back with a cookie to refer an entry it's internal session map..
And further request got forwarded by the proxy to a different express instance say instance_2 which doesn't know anything about the session created inside instance_1.
So it returns 401 and makes your session inconsistent.
There are ways to store db in a common DBs.like an external mongo.
Please google and Try that..
I am hosting a Nest.js express server that uses passport.js to authenticate a user.
The current flow is as follows:
https://backend.com/api/login -> OAuth2 Login Process -> Redirect to https://backend.com/
A cookie is set in the browser that contains a session id that is stored in the backend database.
The config with express-session and prisma-session-store
app.use(
session({
cookie: {
maxAge: 7 * 24 * 60 * 60 * 1000 // ms
},
secret: process.env.SESSION_SECRET ?? '',
resave: true,
saveUninitialized: true,
store: new PrismaSessionStore(prismaService, {
checkPeriod: 2 * 60 * 1000, //ms
dbRecordIdIsSessionId: true,
dbRecordIdFunction: undefined
})
})
);
Now all of this works fine when the user is navigating using the backend routes in the browser. However, I intended on hosting a frontend on a separate domain, where a user can login, go through the backend OAuth2 process, and somehow be authorized to do things on the frontend.
So the new flow would be something like this:
https://frontend.com/ (User clicks login button that calls GET backend.com/login) -> OAuth2 Login -> Redirect to https://frontend.com/home with some credentials
But as far as I learned, setting a cookie from a different domain is a big no no due to security. However I have seen some hacky solutions but I would like to stay away from bad practices.
So my question is, in order to accomplish what I want, do I have to host the backend & frontend on the same domain, possibly different sub domains? Is there really no way to fully separate the front/backend if I want a user to go through an OAuth2 flow?
I'm stumped on this. Most solutions I've seen mention just putting both applications on the same domain.
I came across this helpful article on this subject:
https://www.scottbrady91.com/oauth/cheat-sheet-oauth-for-browser-based-applications
I think I was going for an Implicit Auth flow, but the API and OAuth stuff is being done on the same server.
For the record, when I setup a button on the frontend to call the backend/login endpoint, I get this CORS error which is another problem. Even though I allowed my domain, the OAuth2 redirect is not happy with it.
Access to XMLHttpRequest at 'https://www.twitch.tv/login?client_id=xxx'
(redirected from 'https://backend.com/api/auth/twitch/login') from
origin 'https://frontend.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
I don't really see a simple solution to this as I don't have control over the server that authenticates a user.
Background:
I have a nodejs app using express-session for session management. Recently we had a security checkup for the app and started using secure cookies. Here is the express-session configurations:
server.use(
session({
saveUninitialized: true,
rolling: true,
resave: true,
proxy: true,
secret: envConfig.sessionSecret,
cookie: {
maxAge: envConfig.sessionCookie.maxAge,
httpOnly: envConfig.sessionCookie.httpOnly, // was false before security checkup
sameSite: true, // was false before security checkup
secure: envConfig.sessionCookie.secure // was false before security checkup
},
name: envConfig.sessionKey,
store: new MongoDBStore({
uri: envConfig.sessMongoDB.uri,
collection: envConfig.sessMongoDB.collection,
expires: envConfig.sessMongoDB.expires,
connectionOptions: {
useNewUrlParser: true,
useUnifiedTopology: true
}
})
})
);
The problem:
The issue starts here. This session management works well and stable. But if we share any link through mail or whatsapp web or any messaging app and anyone clicks that link. They will loose there session and in another word the person signs out from the application.
Seems that the browser is not sharing current cookies with the newly opened tab and generates a new session.
Has anyone experienced this issue before and how can we overcome this issue?
UPDATE 1: Seems that the issue is with WhatsApp web. When we open the link through WhatsApp web we loose the session
I managed to solve this problem 1 day after I posted this question. I forgot to share. The issue was about the cookies SameSite policy.
If you put strict value to SameSite. It will cause cookie loose if the you open the webpage from a link that is on another host.
From MDN:
Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party websites.
More information can be found here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
We are testing the situation of sending and receiving cookies with the server.
When the client makes a request to the server, it reads the cookie in the response and tries to use it for the next request, and the client and server have different domains.
SameSite policy has changed since Chrome 80 version, so I set it as follows.
Javascript (client), Axios
axios.post("https://[SERVER_URL]", {}, {
withCredentials: true,
}),
Node.js (server)
res.cookie("test", "cookieTest", {
httpOnly: false,
sameSite: "none",
secure: true,
});
Both client/server are running in HTTPS environment.
I tested it in this situation, but in the Response Cookies in the Network tab of Chrome's developer tools, the cookie given by the server is checked.
However, it is not stored in the cookie storage, and even if you try to read the value with document.cookie, it cannot be read.
I would like to ask if there is anything I missed or if I am testing because of the wrong approach.
What is the way to configure sails.js to set secure cookies? We are using redis to persist session state. The sails.js prescribed way (rather than some Express middleware option) is desired. Ultimately, I want the "secure" column in the Chrome cookies view to be checked for the app's cookie:
In the docs, there is no explicit mention of how to do this:
http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.session.html
There is an ssl config option, but deploying the app with ssl: true did not produce the desired result:
module.exports.session = {
...
ssl: true
...
}
The ssl option isn't documented either, but I assume it has something to do with signing cookies instead.
edit: in the screen shot, I'm serving from localhost without HTTPS, but this app is being served from a production server using HTTPS and the same behavior is observed
Sails uses express.session to handle session cookies, therefore you can enable secure cookies by setting cookie: { secure: true } in config/session.js
You need to use HTTPS for express to set the cookie
it requires an https-enabled website, i.e., HTTPS is necessary for secure
cookies. If secure is set, and you access your site over HTTP,
the cookie will not be set.
If you are behind a proxy that does SSL termination on behalf of your web server enable express trust proxy option by adding the following middleware in config/http.js
module.exports.http = {
customMiddleware: function(app) {
app.enable('trust proxy');
}
};
It appears that there is not a way to do this currently. If you look at the sails.js session implementation here (https://github.com/balderdashy/sails/blob/98522d0bc5df5e6bc30b4dc35708ae71cf4625e2/lib/hooks/session/index.js) you'll see that there is, in fact, no secure-mode stuff whatsoever :(
Since sails is using their own session store implementation, and not piggybacking off of node-client-sessions or express-sessions, the only way to solve this (I think) would be to submit a PR to the sails people.
Sorry!
You can set signed cookies like so
Adding a signed cookie named "chocolatechip" with value "Yummy:
res.cookie('chocolatechip', 'Yummy', {signed:true});
Retrieving the cookie:
req.signedCookies.chocolatechip; //"Yummy"
check out the sails Documentation