I've developed an app using React, Mongodb, Node, and Express and i'm using sessions for authentication. On the backend, I can successfully set a cookie a store a userID however when I try to make requests from the react app using axios, the userID session data is not sent.
This is my session config on the backend:
app.use(session({
name: SESS_NAME || "***",
secret: SESS_SECRET || "***",
saveUninitialized: false,
resave: false,
store: new MongoStore({
mongooseConnection: db,
collection: 'session',
ttl: parseInt(SESS_LIFETIME) / 1000 || (60 * 60 * 48) / 1000
}),
cookie: {
sameSite: true,
path: '/',
domain: APP_DOMAIN || 'localhost:4000',
secure: false, //NODE_ENV === 'production',
maxAge: parseInt(SESS_LIFETIME) || 60 * 60 * 48
}
}));
My cors is configured as follows:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:4000");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Credentials", true);
next();
});
const corsConfig = {
origin:['http://localhost:3000', 'http://localhost:4000' ],
methods:['GET','POST'],
credentials: true,
};
app.use(cors(corsConfig));
after logging in I set the userID in my user Routes, which I confirmed is set by console logging:
req.session.userId = user._id;
This is an example request from my react app
axios.get('/user/auth', { withCredentials: true })
.then(response => {
...
Note that my app is being served on the same domain - I'm using the node app to serve the react app. my folder structure is similar to the following
- APP PARENT FOLDER
-CLIENT //Whole react app
-MODELS
-ROUTES
-SERVER.JS
-PACKAGE.JSON
-NODE_MODULES
I have searched everywhere and tried everything I can think of... no luck. I would appreciate any help!
I've already checked articles on stack overflow and tried to implement the fixes but have had no luck. I've tried the following fixes:
// in my index.js file
axios.defaults.withCredentials = true;
and adding
{ withCredentials: true }
to all of my requests
When I take the userid created at login (ex: 5d6b5d2b09f7d1332543ba90) and manually plug this into the user Route on the backend then everything works fine.
When I console log the session on every request made by the react app it looks like this:
Session {
cookie:
{ path: '/',
_expires: 2019-09-05T05:38:09.657Z,
originalMaxAge: 172800,
httpOnly: true,
sameSite: true,
domain: 'localhost:4000',
secure: false },
}
When it should look like this:
Session {
cookie:
{ path: '/',
_expires: 2019-09-05T05:38:09.657Z,
originalMaxAge: 172800,
httpOnly: true,
sameSite: true,
domain: 'localhost:4000',
secure: false },
userId: 5d6b5c2b03f7d5532543ba90 }
Related
Background:
I use express-session to do authetication in my MERN project.
After I logged in, a session will be created and sessionId will be send to client side by cookie. So when I am trying to fetch data, server will verify my sessionId.
I run my project in dev-env, and everything goes well. I can get the cookies in chrome devtools.
BUT after I deployed my project on Vercel(Server and Client depolyed separately). When I login in my web, there are no cookies in devtools.Verification failed, no sessionId returned.
Below are my codes:
server side:
//setup express-session
app.use(
session({
secret: process.env.SESSION_SECRET,
store: store,
resave: false,
saveUninitialized: false,
cookie: {
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "PRODUCTION" ? true : false,
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
},
name: "rd_sid",
})
);
//setup cors
const corsOptions = {
origin: process.env.CORS_ORIGIN.split(",").map(
(origin) => new RegExp(origin)
),
allowedHeaders: "Origin, X-Requested-With, Content-Type, Accept",
credentials: true,
};
app.use(cors(corsOptions));
client side:
export default function ajax(url, data = {}, method = "GET") {
return new Promise((resolve, isRejected) => {
let promise;
// 1.1 Execute ajax request
if (method === "GET") {
promise = axios.get(url, {
params: data,
withCredentials: true,
});
} else if (method === "POST") {
promise = axios.post(url, data, { withCredentials: true });
}
promise
.then((response) => {
// 1.2 If success, call resolve(value)
resolve(response.data);
})
.catch((error) => {
// 1.3 If fail, do not call reject(reason) but alert error message.
message.error("request error:" + error.message);
});
});
}
I set withCredentials to ture, and also set the header for cors. So it will solve the cross domain problem.
I try to switch secure , httpOnly, and change the cookie's name. But they seem not work.
I tried to see if my cookies is working ,so here's my code
const RedisStore = connectRedis(session)
const redisClient = redis.createClient()
app.use(
session({
//name: 'qid',
store: new RedisStore({ //ttl: how long it should last
client: redisClient,
//disableTTL :true, //make sure session last forever
//disableTouch: true, // make sure it does'nt have to update the last time it's ttl
}),
cookie:{
maxAge: 1000*60*60*24*365*10, //10 years
path: "/"
//httpOnly:true, //javascript front end can't access
//sameSite:'none', // csrf
//secure:false
//secure: __prod__ //cookie only works in https
},
saveUninitialized:true, //automatically create a empty session on default
secret: 'some secret', //env
resave: false,
})
)
app.listen(4000,()=>{
console.log('server stared on localhost:4000')
})
app.get('/products', (req,res,next) => {
console.log(req.session);
if(!req.session.userId){
req.session.userId = 1
}else{
req.session.userId = req.session.userId +1
}
console.log(req.session.userId) //test if work
res.send("hello")
})
So here's the thing, when I connect to localhost:4000/products, In the cookie session, I can only see these
But when I print out the results on vscode console, I can see the number is growing like below , so I do have a session, it's just not showing on the browser , can anyone tell me why is that?
server stared on localhost:4000
Session {
cookie: {
path: '/',
_expires: 2031-08-18T12:59:30.827Z,
originalMaxAge: 315360000000,
httpOnly: true
},
userId: 10
}
11
Session {
cookie: {
path: '/',
_expires: 2031-08-18T13:00:37.257Z,
originalMaxAge: 315360000000,
httpOnly: true
},
userId: 11
}
12
So I got a solution after a lot of tests, So if you only set your cookie to same-site:"none" without secure options ,it would be like my situation,but if you want to turn on secure option your endpoint have to be https, so I don't think this was the answer, and you can change to lax or other options it would act normal in your localhost,
Work in local host
lax
(don't set same site)
But due to secure policy https://www.chromium.org/updates/same-site you can't not pass cookie to some certain website (in my case I want to test cookies in my graphql apollo studio) without setting same-site:"none" secure, so I use mkcert to use https in my localhost
https://web.dev/how-to-use-local-https/ , and everything works,
Work
samesite : none
secure : true
https:yourendpoint
I have a NextJS client running on localhost:3001 and a Express/MongoDB server running on localhost:3000.
For authentication I'm using express-session with connect-mongo like so:
app.use(session({
secret: 'jordan-peterson-is-a-fraud',
resave: false,
saveUninitialized: false,
unset: 'destroy',
cookie: {
httpOnly: false
},
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
On login I set req.session.user = userID, which seems to work fine: it registers a new session record in the sessions table in my database, and sends a set-cookie header with the value connect.sid=<encrypted-session-ID> to the client which gets stored in a session cookie.
So far, so good.
But on logout it seems that calling req.session.destroy() has no effect whatsoever. The client sends a POST with credentials to /logout on the server:
fetch('http://localhost:3000/logout', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: 'cors',
credentials: 'include'
})
And the server does seem to receive a correct req object that includes:
{
...
sessionID: '<encrypted-session-ID>',
session: Session {
cookie: {
path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: false
}
}
...
}
The console also prints [Function: destroy] when I log req.session.destroy. But nothing happens when I call it. The database is unchanged - with the session record still there from the login.
router.all('/logout', async function(req, res){
if (req.session) {
req.session.destroy();
return res.end();
}
}
Anyone know what I'm doing wrong here?
You can use delete req.session.user;
req.session.destroy(req.sessionID)
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
}
I am using passport-twitter to set up a twitter connect on my site. Users can connect by clicking on 'login' or on 'add new item'. The only difference between the 2 is that if they click on add new item, a modal window is supposed to open once theyre logged in.
To know on button they click, I store the url in req.session.referrer:
// route for twitter authentication and login
app.get('/auth/twitter', function(req, res, next){
req.session.referrer = req.url;
console.log(req.session);
passport.authenticate('twitter')(req, res, next);
});
app.get('/auth/twitter/new', function(req, res, next){
req.session.referrer = req.url;
console.log(req.session);
passport.authenticate('twitter')(req, res, next);
});
// handle the callback after twitter has authenticated the user
app.get('/auth/twitter/callback', function(req, res, next){
var options = {
successRedirect : '/twitter-user/signin',
failureRedirect : '/'
};
console.log(req.session);
if (req.session.referrer && req.session.referrer.indexOf('new') > -1) options.successRedirect = '/twitter-user/new';
passport.authenticate('twitter', options)(req, res, next)
});
Everything works fine in my development environment but once online I get this error message:
Express
500 Error: Failed to find request token in session
at Strategy.OAuthStrategy.authenticate (/app/node_modules/passport-twitter/node_modules/passport-oauth1/lib/strategy.js:142:54)
...
My settings are set up properly in Twitter. Here is what I get with the logs:
For the request:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {},
referrer: '/auth/twitter' }
For the callback:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {} }
Maybe it could be due to subdomain problem ( http://example.com vs http://www.example.com) as I don't have the pb locally.
How can I fix this?
Many thanks
EDIT: My API key is set up like this (as per this tutorial: http://scotch.io/tutorials/javascript/easy-node-authentication-twitter):
passport.use(new TwitterStrategy({
consumerKey : configAuth.twitterAuth.consumerKey,
consumerSecret : configAuth.twitterAuth.consumerSecret,
callbackURL : configAuth.twitterAuth.callbackURL
},function(token, tokenSecret, profile, done) {
...
});
First if your callback is localhost and you are using express-session make sure the cookie secure option is set to false. e.g
// Express session
let sess = {
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {
secure: false,
}
}
You can also change that in production by
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1) // trust first proxy
sess.cookie.secure = true // serve secure cookies
}
If that didn't work then check if you have set the cookie sameSite option to Strict. Change it to Lax. e.g
let sess = {
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {
secure: false,
sameSite: 'Lax'
}
}
I got the same Error but solved it.. and here's what my issue was and the solution.
Actuality, it didn't like my redirect url "http:// localhost /auth/twitter/callback". I changed it to "http:// 127.0.0.1 /auth/twitter/callback". In my actual code, I had to keep it as localhost or I'd get errors about a missing token