My attempts at logging in are not getting saved to express session in production. I am saving the session in Mongo Store and the sessions are coming up in MongoAtlas as modified (they way they should appear), but for some reason the server is not recognizing that there is an existing session and is making a new one. When I enable express-session debug, it logs express-session no SID sent, generating session on each request to the server. This makes me think that the session id isn't getting sent with the request and that the problem has something to do with my client and server being on different domains (my client address is https://example.com and my server is on https://app.example.com. I originally had my client on https://www.example.com but changed it thinking that the cookie was getting mistaken for a 3rd party cookie (maybe it still is).
My client is hosted on Firebase Hosting and my Express server is hosted on Google Cloud Run
my express-session settings
app.set('trust proxy', true)
app.use(session({
secret: 'myappisasecret',
resave: false,
saveUninitialized: false,
secure: true,
store: new MongoStore({mongooseConnection: mongoose.connection}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
sameSite: 'lax',
secure: true,
domain: 'mysite.com'
},
proxy: true // I think this makes the trust proxy be useless
}))
Below is my coors server stuff. This code is located above the code above, but I don't think it is causing any issues, but think that it might be important to include.
let whitelist = ['https://app.example.com', 'https://www.example.com', 'https://example.web.app', 'https://example.com']
let corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1 || origin === undefined) {
callback(null, true)
} else {
console.log('Request Origin blocked: ', origin)
callback(new Error('Request blocked by CORS'))
}
},
credentials: true
}
app.use(cookieParser('myappisasecret'))
app.use(cors(corsOptions))
Since the server wasn't receiving a session id, I thought that maybe my client wasn't sending one so I added credentials: 'include' to my client request code
const reqHeaders = {
headers: {
"Content-Type": "application/json"
},
credentials: 'include' as any,
method: "GET"
}
fetch('https://app.example.com/u/loggedIn', reqHeaders)
.then(res => etc...
When this request gets submitted expression-session debug logs:
express-session saving z3ndMizKoxivXR0N9LBZYkPhDG65uvF2 and then
express-session split response
This makes me think that as it tries to save my user data to the session, it gets overwritten at the same time with an initial session data. I have set resave: false. But even then I still get express-session no SID sent with every request sent to the server.
Apparently when hosting with Firebase and Cloud Run cookie headers get stripped due to Google's CDN cache behavior.
Here's the documentation that describes that:
https://firebase.google.com/docs/hosting/manage-cache#using_cookies
I have no clue how to implement sessions now. F
Related
I am learning and applying authentication with google Oauth2.0, and i am using with passport including express-session to handle login session, however, i am having trouble with Firefox and Brave after deploying, the cookie is not send with the fetch call, i check in the devtool the cookie is set successfully with Oauth callback, but i notice in the request header from my fetch call the cookie is indeed absent. My code settings are as follows:
App js :
//require setting
const app = express();
app.use(helmet());
app.use(express.json());
app.use(cors({
origin: process.env.FRONTEND_URL,
methods: "GET, POST, PUT, DELETE",
credentials: true
}));
app.set('trust proxy', 1)
app.use(session({
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: "auto",
maxAge: 1000 * 60 * 60 * 2,
sameSite: "none", //false in developement
},
store: pgStore, // this is working
}));
app.use(passport.initialize());
app.use(passport.session());
app.use("/v1", api);
My fetch request from the client side which I use to check if the user is authentificated, I will get the userdata, otherwise i do nothing
async function httpGetUser() {
return await fetch(`${REACT_APP_BASE_URL}/auth/login`, {
credentials: "include",
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
}
})
};
Well in Chrome browser looks like it work fine, but if it doesn't work with firefox and brave that mean i must do something wrong and i wasn't able to figure out despite some research on the documentation.
UPDATE2: after fighting with cookie for 3 days: Chrome and Edge work without issue, for some unknown reason even thought the cookie is set on the browser after the callback response header, they're not blocked since i can see them on Brave and Firefox, but i just can't fetch with the credentials on the request header despite checking the sameSite attribut in express-session, cors..
UPDATE3: I manage to make it work with Firefox but it's random, if i clear out the cookie it could stop working but sometimes it rework miraculously.. as for Brave still impossible.
New sessions are created for every page, I'm setting the token in the server-side session after the login. But it is not available on the next page a new session is created on that page How to avoid this? The database disconnects after the API call on the page. Every time the database connection is made before calling the API. I require the token throughout the login.
src/index.ts
...
const session = require("express-session")
app.use(session({
name : 'Server_ID',
secret : 'something',
resave:false,
saveUninitialized: true,
rolling: false,
cookie: {
same site: true,
// secure: "development",
httpOnly: true,
secure: false,
maxAge:1000 * 60 * 10
}
}));
...
you must set credentials flag with your API call
for example if you're using axios, you do this:
axios.defaults.withCredentials = true;
with this approach cookie is going to sent along with your request call
make sure you have correct CORS settings too.
This might seem like a redundant question, but please hear me out first:
I'm working with a React Frontend and a Node Backend. I'm using JWT to deal with user authentication. Right now, I'm having trouble actually working with the JWT and performing the authentication. Here's where I'm stuck:
~ I try setting the token as an http cookie in my backend. If i work with postman, I see the token being set. However, when I use req.cookies.token to try and receive the token cookie to perform validation in the backend, I get an undefined value. Am I supposed to be sending the cookie from the frontend to the backend somehow? I feel like this is the part that I am missing.
Please advise!
SO I can give you an alternative solution to handling session making use of express-session and connect-mongodb-session this has tend to been the popular and somewhat secure solution for server session handling
Firstly you will need the following packages
npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session
Now that we have packages that we need to setup our mongoStore and express-session middleware:
//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);
const store = new mongoStore({
collection: "userSessions",
uri: process.env.mongoURI,
expires: 1000,
});
app.use(
expressSession({
name: "SESS_NAME",
secret: "SESS_SECRET",
store: store,
saveUninitialized: false,
resave: false,
cookie: {
sameSite: false,
secure: process.env.NODE_ENV === "production",
maxAge: 1000,
httpOnly: true,
},
})
);
Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server
//Still you index.js/server.js (Server entry point)
app.use(
cors({
origin: "http://localhost:3000",
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true,
})
);
Now our middlewares are all setup now lets look at your login route
router.post('/api/login', (req, res)=>{
//Do all your logic and now below is how you would send down the cooki
//Note that "user" is the retrieved user when you were validating in logic
// So now you want to add user info to cookie so to validate in future
const sessionUser = {
id: user._id,
username: user.username,
email: user.email,
};
//Saving the info req session and this will automatically save in your mongoDB as configured up in sever.js(Server entry point)
request.session.user = sessionUser;
//Now we send down the session cookie to client
response.send(request.session.sessionID);
})
Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:
Code below: React App where you handling logging in
//So you will have all your form logic and validation and below
//You will have a function that will send request to server
const login = () => {
const data = new FormData();
data.append("username", username);
data.append("password", password);
axios.post("http://localhost:5000/api/user-login", data, {
withCredentials: true, // Now this is was the missing piece in the client side
});
};
Now with all this you have now server sessions cookies as httpOnly
I'm facing an issue trying to implement HTTPS via a proxy (initially NGINX, now AWS ALB) to secure connections to my node server. My login request is being processed just fine, but subsequent requests after login are being marked as isAuthenticated === false, and therefore my node app is returning 401.
I know isAuthenticated === false is being caused by the session cookie not being set in the browser, however I can't seem to figure out why the session cookie isn't being set (or sent?).
I'm configuring express-session as below:
app.use(cookieParser('secret'));
app.set('trust proxy', 1);
// add & configure middleware
app.use(session({
name: 'session',
store: new redisStore(),
secret: 'secret',
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true,
secure: true }
}));
My node app is sitting behind an AWS Application Load Balancer which communicates with the app over HTTP, so I've configured trust proxy to 1.
My passport configuration and local strategy are just below that:
// configure passport.js to use the local strategy
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(email, password, done) => {
mysql.getConnection((err, conn) => {
conn.query('SELECT u.user_id, u.email, u.password, o.uuid FROM user AS u INNER JOIN organization AS o ON o.id = u.org_id WHERE email = ?;', [email], ( err, rows ) => {
if (err) throw err;
if (rows.length !== 1)
return done(null, false, { message: 'User does not exist.\n' });
else if (!bcrypt.compareSync(password, rows[0].password))
return done(null, false, { message: 'Incorrect password.\n' });
else{
return done(null, {user_id: rows[0].user_id, email: email, uuid: rows[0].uuid});
}
});
conn.release();
});
}
));
The request coming from the react client is:
axios.post('https://sub.mydomain.com' + '/api/login', userObj, {withCredentials: true})
.then(res => {
if (res.status === 200) {
initUser(res.data, true);
}
else {
this.setState({errors: {password: 'Incorrect username or password'}})
this.props.history.push('/login');
}
})
.catch((e) => {
this.setState({errors: {password: 'Unable to login'}})
this.props.history.push('/login');
});
When making the request I'm getting returned a 200 status with the user information as I would expect. See the screenshots from PostMan below:
Also these are the response headers in Chrome:
access-control-allow-credentials: true
access-control-allow-origin: https://sub.mydomain.com
content-length: 89
content-type: text/html; charset=utf-8
date: Tue, 12 Mar 2019 07:49:45 GMT
etag: W/"59-T0xi+VpB6A/MLCHMb8Qz3Pq9dwc"
status: 200
vary: Origin
x-powered-by: Express
Somewhere along the line it seems the session cookie is either failing to be sent from the node app, or set in the browser.
I'm running the exact same on my local machine and the cookie is being set in the browser no problem. The only difference between my local and my server is the ALB in front of the node application, and I'm setting secure: false on my local (as I'm not using HTTPS).
FYI - I had initially tried using an NGINX proxy_pass on my server to handle the HTTPS connections and had the same issue. I had tried removing the proxy pass and using the AWS Application Load Balancer after failing to find a solution with NGINX (I thought there may be an issue with my NGINX config). This leads me to believe the issue is with the express-session configuration as its the common denominator here, however I may be missing something else.
FYI 2 - I've tried setting trust proxy to true, 1, 2, 3, etc, Also tried setting secure to false, Also tried setting httpOnly to false - and countless variations of the above, but still I don't seem to be getting the cookie in the browser.
FYI 3 - I tried removing the proxy and reverting back to a direct HTTP connection to the Node application and I'm still not getting the cookie on in my browser (or in PostMan).
Hoping someone here can point out something I've missed in the hopes of getting this resolved.
Thanks in advance!
So after days of investigating, the issue turned out to be that my Redis server had terminated and failed to restart. facepalm
The sessionID wasn't being generated (or stored) as it couldn't connect to the Redis store to save the session information on the server side.
Starting the Redis server solved the issue immediately, however it would be great if express/passport had some verbose logging to notify users of this.
Hope someone else finds this useful!
I'm using NodeJS + express + express-session to persist a userID from anywhere in the application.
On the first route, my session is defined
userProfileRoutes.route('/authentication').post((req, res) => {
req.session.userID = 10; //example
console.log(req.session)
}
The result of the console.log is:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: true },
userID: 10 } // this is the right value
But then, from a different route, I can't see the value:
userProfileRoutes.route('/edit').get(function (req, res) {
console.log('After the nav edit route');
console.log(req.session);
}
And this prints
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: true }
} // ID VARIABLE DISAPEARS HERE
I am configuring express-session using these parameters:
app.use(session({
secret: 'secret',
proxy: true,
resave: false,
saveUninitialized: true,
withCredentials: true,
cookie: { secure: true },
store: new MongoStore({ mongooseConnection: db })
}));
Why is my userID not persisted between requests and on all routes?
You are setting cookie: {secure: true} but trying to access your server using HTTP.
From the express-session documentation:
cookie.secure
Note be careful when setting this to true, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.
Please note that secure: true is a recommended option. However, 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.
Make sure you are either using HTTPS (always in production!) or you set cookie.secure to false (maybe, and for development only!)
The secure flag in cookies
The secure flag is an option that can be set by the application server when sending a new cookie to the user within an HTTP Response. The purpose of the secure flag is to prevent cookies from being observed by unauthorized parties due to the transmission of a the cookie in clear text.
To accomplish this goal, browsers which support the secure flag will only send cookies with the secure flag when the request is going to a HTTPS page. Said in another way, the browser will not send a cookie with the secure flag set over an unencrypted HTTP request.
By setting the secure flag, the browser will prevent the transmission of a cookie over an unencrypted channel.
from https://www.owasp.org/index.php/SecureFlag
Cookies in express-session
Following common practice, express-session uses cookies to store a session ID and server side storage (mongoDB in your case) to store session data. If the browser does not send your session ID because it can't find a valid cookie, your server will assume there is no session, and save the user id on a new session on every request.
When you got to /authentication it will save the ID on a new session. When you try to read in in a different request, the session ID has changed and you have no value in userID.