I'm having a really odd issue with session storage using mongoDB and passport.
When a user logs in, the session is created and passed to mongo as expected as well as the passport id.
{
"_id" : "GEEFIDhiMehdjPvtxRmPy_Kuls2IdVsx",
"expires" : ISODate("2020-06-10T03:09:30.396Z"),
"session" : "{\"cookie\":{\"originalMaxAge\":28800000,\"expires\":\"2020-06-10T03:09:29.358Z\",\"httpOnly\":true,\"path\":\"/\"},\"passport\":{\"user\":\"ebf7d73d-f3f2-4f96-8123-3f0f262ffff6\"}}"
}
However, when the express server is restarted passport clears the user out of the sessions storage when a user selects a route that requires auth (there by invoking the isAuth function). Meaning users are required to login in after server restart.
{
"_id" : "GEEFIDhiMehdjPvtxRmPy_Kuls2IdVsx",
"expires" : ISODate("2020-06-10T03:11:54.464Z"),
"session" : "{\"cookie\":{\"originalMaxAge\":28800000,\"expires\":\"2020-06-10T03:11:54.464Z\",\"httpOnly\":true,\"path\":\"/\"},\"passport\":{}}"
}
My auth code is pretty standard stuff tbh, where am I going wrong here? I'm using the azure-ad passport strategy.
const passport = require('passport');
const OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const config = require('../config');
const store = new MongoStore({
url: config.databaseUri,
});
// Usual passport code here findbyoid, ensureAuthenticated etc
function setupPassport(app) {
app.use(
session({
secret: 'somepassword',
resave: true,
cookie: {
maxAge: 8 * 60 * 60 * 1000,
},
saveUninitialized: true,
store: store,
})
);
app.use(passport.initialize());
app.use(passport.session());
}
I wasn't actually storing the correct user information in the session or even looking in the session for the user information. Instead I was storing users in a local array and looking up their oid with deserializeUser function. the solution was to replace this with the code below.
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
done(null, user);
});
This way the user object was stored in the session database and then recollected when a user checked auth.
Related
When an administrator blocks a user, I want to disconnect his active session, so that he cannot using the application until the session ends, something like that:
app.post('/admin/users/block-user', (req, res) => {
const { userId } = req.body;
UsersModel.update({ status: 'blocked' }, { where: { id: userId } });
passport.forceLogout(userId)// << ??
})
how do I do it?
Basically you have to use connect-mongostore to store the sessions of each user when they log in. Then you use the existing mongoose connection to do a raw mongodb query to delete a specific user session based on user_id, after hitting the logout api. They will be logged out the next thing they try to do that requires user information on the site.
In app.js:
var session = require('express-session');
var MongoStore = require('connect-mongostore')(session);
app.use(require('express-session')({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
store: new MongoStore({mongooseConnection: mongoose.connection})
}));
in my controller file:
mongoose.connection.db.collection('sessions').deleteMany({
"session.passport.user": username
})
To capture the issue - I am using youtube's data API OAuth for authenticating users and authorizing my app. to keep track of users and their tokens I set up a mongo dB to store sessions as well as app data (users metadata and tokens included). the problem I have is that once a user had authenticated the app, I can see the session data properly stored in the session store, including user id, but when I make consecutive calls to my backend - the user is not logged in.
I'll describe the flow of what I did and then add some code snippets to make it a bit more clear (hopefully):
The client initiates the OAuth flow by redirecting to the https://www.googleapis.com/auth/youtube with all required parameters. once the user had authorized the app - the redirection URI (specified in google API dashboard) redirect the result to my backend.
on my backend, I use express-session and passport js with the passport-youtube-v3 strategy. as mentioned, the login/signup part seems to be working fine. I can see the a new user is registered in my users' collection and the session stored includes that user id. I can also see the session is stored in a cookie on the client. To the best of my understanding - once a consecutive API call had been initiated - the session cookie should be included in the request and passport should authenticate the user. however, when I guard any endpoint with passport.authenticate('youtube') or a simple req.isAuthenticted() check - the request stops there and the next middleware or function is not invoked (meaning the user is not logged in I assume). if I remove these guards (for the sake of debugging) what I am getting on the request object seems like a whole new session altogether. it has a different expiry timestamp, missing user data (or the cookie property).
here is a bit of code to illustrate what I am doing, I am surely doing something wrong - I just can't figure out what...
server.js
const mongoStore = require("connect-mongo")(session);
const sessionStore = new mongoStore({
mongooseConnection: mongoose.connection,
collection: "sessions",
});
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
maxAge: 1000 * 60 * 60 * 24, // 1 day
},
})
);
app.use(passport.initialize());
app.use(passport.session());
// set up routes ...
passport.js
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
console.log("deserializeUser", id);
userModel
.findById(id)
.then((user) => {
done(null, user);
})
.catch((err) => {
done(err);
});
});
passport.use(
new YoutubeV3Strategy(
{
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/youtube/redirect",
scope: ["https://www.googleapis.com/auth/youtube"],
authorizationParams: {
access_type: "offline",
},
},
async function (accessToken, refreshToken, profile, done) {
// console.log("passport verify cb", profile);
let user = await getUser(profile.id);
// console.log("user", user);
if (!user) {
user = await addUser({
youtubeId: profile.id,
name: profile.displayName,
accessToken: accessToken,
refreshToken: refreshToken,
});
}
return done(null, user);
}
)
);
an example for a route called after user authorized the app and is registered in db (both in user collection and a session created in session store):
router.get("/someendpoint", (req, res) => {
console.log("isAuthenticated", req.isAuthenticated()); // false
console.log("session", req.session); // a session object that does not correspond to anything in my session store.
});
As mentioned, if I guard this endpoint with passport.authenticate('youtube') or a simple req.isAuthenticated() check the code doesn't get anywhere past that guard.
some additional info that helps you help me:
my client and server run on localhost but on different ports
the 'some endpoint' endpoint is on a different path than the endpoints I use for managing the OAuth flow.
Your help is much appreciated...
I've only been working with express and react for a little while. The first app I worked was set up pretty well. The new one I am on is set up well too, but there is a little problem with session when I make a code change.
When I make a change to the code, save, and look at the app, I need to re-login EVERY TIME.
I've looked at a bunch of blogs and SO posts, and most seem to be trying to solve session problems in a prod environment with stores like Redis, etc.
It would seem to me, that express-session should be suitable to do this in a development environment. No?
This is what index.js looks like:
/*
Configure the Google strategy used by Passport.
OAuth 2.0-based strategies require a `verify` function which receives the
credential (`accessToken`) for accessing the Google API on the user's
behalf, along with the user's profile. The function must invoke `callback`
with a user object, which will be set at `req.user` in route handlers after
authentication. ie. callback(err, user)
*/
passport.use(new GoogleStrategy({
clientID: `${process.env.GOOGLE_AUTH_CLIENT_ID}`,
clientSecret: `${process.env.GOOGLE_AUTH_CLIENT_SECRET}`,
callbackURL: `${process.env.GOOGLE_AUTH_CALLBACK_URL}`
},
(accessToken, refreshToken, idToken, profile, callback) => {
const onSuccess = (res) => {
/*
res.data = {
refresh_jwt: refresh token,
api_jwt: access token
}
*/
callback(null, res.data);
}
const onError = (err) => {
callback(new Error('Google OAuth Failed'), null);
}
getOauthData('google', idToken.id_token, onSuccess, onError);
})
);
/*
Configure Passport authenticated session persistence.
In order to restore authentication state across HTTP requests, Passport needs
to serialize users into and deserialize users out of the session.
*/
passport.serializeUser((user, callback) => {
const sessionUser = {
refresh_jwt: user.refresh_jwt,
api_jwt: user.api_jwt
};
callback(null, sessionUser);
});
passport.deserializeUser((sessionUser, callback) => {
verifyIdentity(sessionUser.api_jwt, sessionUser.refresh_jwt, callback);
});
/* Start app */
var app = express();
app.disable('x-powered-by');
/* -- Application-level middleware -- */
/* Configure session */
app.set("trust proxy", 1);
var sess = {
secret: `${process.env.COOKIE_IDENT_SECRET}`,
name: "ident",
proxy: true,
resave: false,
saveUninitialized: true,
cookie: {
expires: 604800000, // 1 week
secure: true
}
}
app.use(session(sess));
/* Initialize Passport and restore authentication state, if any, from the session. */
app.use(passport.initialize());
app.use(passport.session());
From docs
Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.
The default server-side session storage, MemoryStore
So everytime you restart app, the memory is going to be wiped along with sessions stored. If you need persistent session, store it in some persistent store, db etc, using one of the Session Stores available under Compatible Session Stores in the same link.
An example from connect-mongo using mongodb as persistent storage:
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
app.use(session({
secret: 'foo',
store: new MongoStore(options)
}));
When i close the broswer, the session gets destroyed automatically. Following is the code in my app.js file.
const session = require('express-session');
const MongoSessions = require('connect-mongo')(session);
var mongo = require('mongodb').MongoClient;
var db_url = 'mongodb://localhost:27017/test';
app.use(session({
secret: '007',
resave: false,
saveUninitialized: false,
duration: 40*60 *1000,
activeDuration: 10*60*1000,
store: new MongoSessions({
url: db_url
})
}));
When user logs in , i store the user id of user in a session. When a user again accesses the system, it will redirect him to directly to home page. To check this:
exports.indexPage = function (req, res, next) {
if (req.session.userid == null) {
res.render('login');
} else {
res.render('index');
}
};
It works fine when i keep the browser open but close all tabs and again access the application. When i close the browser and again access the application, it redirects me to login page.
I'm not sure what duration and activeDuration are meant to be, but they aren't valid options for express-session.
Since you're not setting a maxAge value for the session cookie, it automatically becomes limited to the current browser session, meaning that it will be destroyed when you close the browser (as you already noticed).
To prevent that, configure a maximum age (in milliseconds):
app.use(session({
cookie : {
maxAge : 40 * 60 * 1000
},
secret: '007',
...
}));
sorry for newby question, but can you explain me how to use sessions in nodeJS. I read a lot of articles in internet but I didn't success to implement something for my purpose (data is saving the session, but every new request session is empty), can you give example from the beginning how to initialize and how to use.
Purpose: when user do login in the system, I need to open session for him and every request that he will send in the future I need to check is his session exist?
I'm using express 4.x. I do it like:
// init session
app.use(cookieParser());
app.use(session({
secret : "yepiMobileSession",
resave : true,
key : "session",
store: mongooseSession(daoService.mongoose),
saveUninitialized : true
}));
// save user to the session
request.session[CONST.SESSION_USER] = user;
// Check login
function checkLogin(id){
var user = request.session[CONST.SESSION_USER];
if (user && request.params.clientData && user._id == id){
return true;
} else {
return false;
}
}
You can take a look at the following code. I think this will help you.
var app = require('express')(),
expressSession = require('express-session'),
cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(expressSession({
secret: 'mYsEcReTkEy',
resave: true,
saveUninitialized: true
}));// I haven't used the session store
//setting session
app.post('/login', function(req,res){
var id=12345;
req.session.userId = id;
res.send(200);
});
//getting session
app.get('/hello', function(req,res){
var id=req.session.userId;
console.log("Hello",id);
res.send(200);
});
But node server and client have to be in same domain.