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 created a frontend application using React which I then deployed on Netlify. The frontend app interacts with my express backend application which I created and deployed to Render. Any interaction between the frontend and backend is protected by authentication using passportjs. The backend app uses passports local strategy to verify with a mongoDB connection if the user exists/username and password are correct. Once confirmed, I use passports serialize and deserialize functions to maintain a login session.
During development I worked through this application on http://localhost. During this time, everything worked as intended. The login session was saved as a cookie and I was able to send that through the frontend app as part of future requests to the backend app to authenticate the user.
When I deployed my frontend app to Netlify and backend app to Render, the session cookie no longer saved meaning the user could never be authenticated in the backend as every request, after logging in, had no saved session cookie to send along to the backend.
A little more detail, for the express-session, used in combination with passport to create the login session, I used connect-mongo to store the session on a MongoDB. Each time I login through the frontend, I can see the session being created and stored in MongoDB. The problem appears to be that the cookie is not being transferred over in the response from the backend and I am not sure why.
Here is the session options I currently have in my express backend app:
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI }),
cookie: {
secure: true,
sameSite: "none",
httpOnly: true,
expires: 24 * 60*60*1000
}
})
);
I have tried searching around to find a solution and have messed with many of the options including changing secure, changing httpOnly, setting sameSite to false and none, adding a domain which points to the frontend URL and another for the backend URL, removing any security packages that I installed such as helmet and a rate limiter and nothing seems to be working.
I have also set the access control header for credentials to true:
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", process.env.DOMAIN_URL);
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
next();
});
For all my requests I am using fetch. I can across a similar problem to mine but it was not answered: Browser not saving session cookie from cross domain server
Any help or suggestions would be much appreciated!
Solved it. The issue was with trust proxy. Since I am running multiple domain servers, the backend was behind a reverse proxy. By enabling trust proxy, my backend app will understand that its sitting behind a proxy and the X-Forwarded- header fields may be trusted. To enable it I added the following code in my backend app:
app.set("trust proxy", 1);
I used google authentication in my MERN stack app using passport js. I have deployed frontend to vercel and backend to render. in my localhost, I can log in successfully and it returns cookies to my frontend from the backend. but in the deployed version when I try to log in it does log in but it doesn't return any cookies to the frontend. I did some research and found that cookies can't be shared between different domains. my question is should I host my backend in a subdomain and frontend in the main domain?
frontend: https://passport-frontend-seven.vercel.app/
backend : https://popupchat-backend.onrender.com/
// how i used express-session:
app.use(
session({
secret: "secretcode",
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: "none",
secure: true,
domain: process.env.CLIENT_URL,
maxAge: 1000 * 60 * 60 * 24 * 7, // One Week
},
})
);
app.set("trust proxy", 1);
Yes, you are correct in your understanding that cookies can't be shared between different domains. If you have your frontend and backend deployed on different domains, then you will have difficulty accessing the cookies set by the backend in the frontend.
Hosting your backend on a subdomain of your main domain is one solution to this problem. For example, if your main domain is example.com, you can host your backend at api.example.com. This way, both the frontend and the backend will be under the same main domain and you will be able to share cookies between them.
However, there are other solutions as well, such as using a token-based authentication system, where the backend returns a token after a successful login, and the frontend can store this token in local storage or a cookie. The frontend can then send this token in the header of subsequent API requests to authenticate the user on the backend. This approach can be more secure than using cookies, since cookies are often stored in plain text and can be easily compromised.
I have a Node.js API on api.com that authenticates users based on Sessions. How can I save session from api in api.com to client.com?
For example; I'm going to send a request from client.com to api.com and add a session cookie to client.com from the api on api.com.
My session settings;
app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
store
}))
I'm struggling with understanding cookie credentials in detail.
The main issue is that my frontend and backend run on different domains. On localhost, they have different ports (localhost:4200 and localhost:3000), and on my server different subdomain (example.com and api.example.com).
Basic setup:
Angular send post request to login:
this.http.post(environment.apiUrl + 'users', credentials)
NodeJS validates the request and creates an express-session (session in DB and cookie with sessionId):
app.use(session({
secret: process.env.SECRET,
saveUninitialized: false,
store: mongoDBStore,
name: 'node_session',
cookie: {
secure: true,
httpOnly: true,
maxAge: 5 * 60 * 1000
}
}));
app.post('/', (req, res) => {
// some validations
req.session.userId = result._id;
res.send(result._id);
}
My understanding
From my understanding cookies get stored for the server domain. So if both run on the same domain, on each request, the backend can use the session cookie. But with different domains, the session is empty on further requests.
Additionally, I found the cookie node_session in the frontend and backend. So if I add { withCredentials: true } to the requests of Angular, everything works. But in this case, I use the cookie from the frontend.
My Questions:
Is it secure to use the frontend cookie?
Why can I find the session cookie in the frontend and backend?
Why is my backend unable to find the session cookie?
session information is stored on the backend. frontend just stores uniq session identifier in cookies. If you want to understand somehow that requests came from the same user one of the options is to use {withCredentials: true}, without it cookies are just not saved to the client, and backend can not define, whether different queries were send by one client or not.
as an alternative to cookies there is a popular option to use authorization token, but it is a bit more difficult to setup
Basically, I have a sign-in form, running on AUTH.domain.com and I create a session (saved in cookie) for the logged users. Then I redirect the user to APP.domain.com, and I need his session to be passed also to this subdomain, but we can't figure, how to do that.
Authorization works with passport.js, and session set from passport.js
I tried set domain option on cookies middleware and set res.sessionOption.domain = '.domain.com'. But this does work.
app.use(cookiesession({
secret: 'secretstring',
saveUninitialized: false,
resave: true,
domain: '.domain.com'
}));
app.use(passport.initialize());
app.use(passport.session());
req.sessionOptions.domain = '.domain.com';
Try setting the domain to domain.com (note the omission of the preceding .)!