I am having a problem with logging out of my application.
I am using MongoDB to store my sessions.
I log out by using session.destroy and the document gets removed from the collection in the database. However I still remained logged in. Also the cookie in the browser still exists, surely this should expire. I believe my authentication is using the cookie in the browser to check authentication and since that is still there, it keeps me logged in.
Here is my authentication code:
app.js
app.use(session({
secret: 'whatshouldmysecretkeybe',
cookie: {
maxAge: 86400000
},
resave: false,
unset: 'destroy',
saveUninitialized: false,
store: new MongoStore({
mongooseConnection: db
})
}));
loginController.js
// Authenticated Check
exports.requires_login = function (req, res, next) {
if (req.session && req.session.userId) {
return next();
} else {
return res.redirect('/cms/unauthenticated');
return;
}
}
exports.logout = function (req, res, next) {
if (req.session) {
// delete session object
req.session.destroy(function (err) {
if (err) {
return next(err);
} else {
return res.redirect('/cms/');
}
});
}
}
Route file
I put the login_controller.requires_login middleware on the route to check for authentication on every route that needs authentication. I hope this is the best way to do this.
/* GET Login authenticate */
router.get('/users/logout', login_controller.requires_login, login_controller.logout);
/* GET User listing page */
router.get('/users', login_controller.requires_login, user_controller.list);
Thank you.
Looks like the issue was due to mongoose 5 not being fully compatible with connect-mongo.
This github issue explains more:
https://github.com/jdesboeufs/connect-mongo/issues/277
To fix you have to use your mongo url in the url property instead of mongooseConnection.
Example:
app.use(session({
secret: 'whatshouldmysecretkeybe',
resave: false,
unset: 'destroy',
saveUninitialized: false,
store: new MongoStore({
url: YOUR_MONGO_URL
})
}));
Related
I'm running a nodejs/reactjs app on heroku. I implemented google login using Passport. I'm getting an error "Unable to verify authorization state" when people try to login.
I see here NodeJS Express Session isn't being restored between routes in kubernetes that I need to set the X-Forwarded-SSL header. How do I do that according to what the question says?
The solution outlined on that page also mentions Apache, but Heroku doesn't make use of Apache to forward requests to apps' web dynos.
Is anyone running into the same issue on Heroku?
So the weird thing is when I try to login, it works the second time but the first time, I get the error "Unable to verify authorization state".
here's my index.js
const session = require("express-session");
app.use (
session ({
secret: "ddd",
resave: false,
saveUninitialized: true,
cookie: {
expires: 60 * 60 * 24,
secure: (app.get('env') === 'production')
}
})
);
if (app.get('env') === 'production') {
app.set('trust proxy', 1); // trust first proxy
}
So as I mentioned your issue might not be related to the request headers because in my issue, session never persisted, whereas yours does in your second attempt. It might be an issue with your verify function or your deserializeUser function.
Here's an example. I don't use Google auth personally, but something else. My code looks similar, but I got some of the Google auth code from https://github.com/jaredhanson/passport-google/blob/master/examples/signon/app.js. Fill in your stuff where appropriate and debug/log what's coming in to see how your functions are being called.
passport.use(new GoogleStrategy({
returnURL: 'http://localhost:3000/auth/google/return', // replace with yours
realm: 'http://localhost:3000/' // replace with yours
}, function (identifier, profile, done) {
// console.log identifier and profile, or even better, use your debugger on this line and see what's happening
// Do whatever you need to do with identifier and then..
return done(null, profile);
}));
passport.serializeUser(async (user, done) => {
// console.log user or use your debugger on this line and see what's happening
const userInDb = await db.getUser(user) // Find your user in your db using based on whatever is in the user object
const serializedSessionUser = {id: userInDb.id, username: userInDb.username} // this is the object that'll appear in req.user. Customize it like you want
return done(null, serializedSessionUser); // Commit it to the session
});
passport.deserializeUser((user, done) => {
done(null, user);
});
Edit: Apparently there's 2 ways to use Google for passport. The first is OAuth / OAuth 2.0. The second is OpenID. I used OpenID in this example. Adjust accordingly!
Edit 2: Here's my own equivalent to your index.js:
app.use(session({
cookie: {
sameSite: 'lax',
secure: !['development', 'test'].includes(process.env.NODE_ENV),
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
},
proxy: true,
resave: false,
saveUninitialized: true,
secret: process.env.COOKIE_SECRET,
store: 'your store',
rolling: true,
}));
I have a question about session cookies over web from express backend. In the front I am using a Vue PWA/SPA that is being mounted on express static via express.use(express.static('static')); to serve the built files.
I did some research that we can attach options to the function with
express.use(cookieParser());
express.use(session({
secret : `${uuid()}`,
proxy: true,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 30
}
}))
const options = {
dotfiles: 'ignore',
etag: false,
extensions: ['html', 'htm'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function(res, path, stat) {
res.set('Set-Cookie', "myCookie=cookieValue;Path=/");
res.set('x-timestamp', Date.now());
}
}
express.use(express.static('static' , options));
After doing those, hoping I can read myCookie=cookieValue on the browser via document.cookie but I don't see those datra being printed on the page.
I have also tried it with __dirname method as well and then do
app.get("/", function(req, res) {
req.session.fullname = "Oh mi gee";
res.render("index");
});
didn't give me what I want. Is there a way for me to add session/cookies so I can somewhat secure the API calls by passing cookie data on the header for confirmation?
Thanks in advance!
I have figured it out, is to place this express.static at the very end of the express configuration.
before we establish express.static we will want to do
app.use('/path', (req, res, next) => {
req.session.views++;
res.cookie('myCookie', 'cookieValue', {
maxAge: 1000 * 60 * 30
});
console.log(req.session);
next();
})
will transfer the cookies to the client side with the expiry of half an hour.
In my app I restrict some access to some actions and pages if a user is not logged in. I have:
var restrict = function(req, res, next) {
if (!req.user) {
console.log("USER isn't logged in.")
return res.status(403).send('Access or action denied, please log in');
}
next();
}
app.get('/stocks', restrict, MainHandler.findAllStocksFromUser);
app.get('/stocks/:id', MainHandler.findStockByIdAndDates);
app.put('/stocks/:id/stockActions', restrict, MainHandler.handleStockAction);
I'm essentially trying to refresh a session everytime the client makes a request to the server so that the server doesn't logout the user/destroy the session when it shouldn't. For testing, I want the session to expire/the user to be logged out if 20 seconds go by without the user making an requests to the server. I have:
app.use(session({secret: 'secret', saveUninitialized: true, resave: true, expires: new Date(Date.now() + (20000))}));
Then I try to use middleware to refresh the expiration date every time the use makes a request:
// Session-persisted message middleware
app.use(function(req, res, next){
req.session.cookie.expires = new Date(Date.now() + 20000);
next();
});
But if I log in from the client, and click around, causing requests to the server, I still get the log-in error on the client after 20 seconds, despite trying to "refresh" the session in the middleware. I have also tried using maxAge using the same strategy with the middleware. Any ideas? Thanks!
You can try define your session as follows
app.use (
session ({
secret: "secret",
saveUninitialized: true,
resave: true,
cookie: {
expires: 20 * 1000
}
})
);
and then refresh the session using
req.session.touch()
or you could define your session as
app.use (
session ({
secret: "secret",
saveUninitialized: false,
resave: true,
rolling: true,
cookie: {
expires: 20 * 1000
}
})
);
and it will renew the session automatically and it will only expire when it has been idle for the value in the expires variable
express-session supports a duration-based maxAge setting, which will work better than setting a fixed date for all sessions. So your middleware usage should instead look like:
app.use(session({
secret: 'secret',
saveUninitialized: true,
resave: true,
maxAge: 20000
}));
Next, to update the expiration of the session, you can just call req.session.touch(); if that is all you're doing to the session and its contents.
The documentation has a lot of other good information on controlling session expiration and related topics.
I have a working login function that properly authenticates and saves. However, express never remembers the old session, it always creates a new one for every network request.
Evidently, Passport is exceedingly sensitive to the order that express middleware is initialized. (Example: https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive). I checked my config against a number of examples and rearranged it to so if it would help, but it hasn't moved my bug. Either that isn't the issue or I just haven't found the config holy grail yet. Here's my current config:
Express Config
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.static(path.join(config.root, '/views')));
app.set('views', config.root + '/views');
var sessionOpts = {
saveUninitialized: true,
resave: false,
store: new RedisStore({
host: 'localhost',
port: 6379
}),
secret: 'keyboard',
cookie: {
httpOnly: true,
maxAge: 1000
}
}
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cookieParser('keyboard'));
app.use(session(sessionOpts));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
require('./routes/router.js')(app, passport);
Passport Config
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, username, password, done) {
client.hgetall(username, function(err, reply) {
if (err) {
return done(err);
}
if (!reply) {
return done(null, false, {
message: 'Incorrect username.'
})
}
if (reply.password !== password) {
return done(null, false, {
message: 'Incorrect password.'
})
}
return done(null, reply)
})
}));
Does Passport need handholding for Redis? Redis sessions are stored in a 'sess' folder with a key like so: sess:RhodmaK2V2wDNLglV5j1B6rC. All of the tutorials I've found have been about Mongo so I'm not sure if I need to somehow include the session key when trying to look it up. Within the session entry, it's properly stored in standard cookie form for passport though: req.session.passport.user
Is there any way to see what is happening inside of passport initialize? On subsequent requests it is supposed to do this: "The general passport middleware we setup (passport.initialize) is invoked on the request, it finds the passport.user attached to the session. If is doesn't (user is not yet authenticated) it creates it like req.passport.user = {}." See 'SUBSEQUENT AUTHENTICATED REQUESTS FLOW' - http://toon.io/understanding-passportjs-authentication-flow/ I strongly suspect my problem lies at that step, so it would be nice to be able to see inside of it.
Some interesting tidbits:
Passport has never once called deserializeUser. I assume it's never
reached that point.
I have checked the other StackOverflow questions about this problem,
but none of those solutions worked for me.
I've checked to see if the new sessions are generated by any static resources but they are not. It's only for intentional server requests. On the pages without them, no new sessions are made.
All sessions have either a populated or empty req.session.passport property.
Your session ID might be different for every request because you have your cookie set to expire after 1 second. cookie.maxAge here is in ms:
cookie: {
httpOnly: true,
maxAge: 1000
}
Edit: I also have to nag you about storing passwords in plaintext. Don't do this ;)
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