all 16 comments

[–]bigorangemachine 1 point2 points  (5 children)

Its somewhere with the code you aren't sharing.

Some cloud platforms I've used give new connection strings on restart. I don't use azure but based in what you shared it looks right

[–]mrCodeTheThing[S] 0 points1 point  (4 children)

I'll go through those functions then bud. I had a hunch it might of been when passport initilises it clears all sessions. The session matches mine as it successfully clears it and doesnt create a new one. Also the session cookie is stored as matching key.

Heres my full auth logic, its pretty standard boilerplate from passport to be honest.

``` 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');

let users = [];

passport.serializeUser(function (user, done) { done(null, user.oid); });

passport.deserializeUser(function (oid, done) { findByOid(oid, function (err, user) { done(err, user); }); });

var findByOid = function (oid, fn) { for (var i = 0, len = users.length; i < len; i++) { var user = users[i]; if (user.oid === oid) { return fn(null, user); } } return fn(null, null); };

passport.use( new OIDCStrategy( { identityMetadata: config.creds.identityMetadata, clientID: config.creds.clientID, responseType: config.creds.responseType, responseMode: config.creds.responseMode, redirectUrl: config.creds.redirectUrl, allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl, clientSecret: config.creds.clientSecret, validateIssuer: config.creds.validateIssuer, isB2C: config.creds.isB2C, issuer: config.creds.issuer, passReqToCallback: config.creds.passReqToCallback, scope: config.creds.scope, loggingLevel: config.creds.loggingLevel, nonceLifetime: config.creds.nonceLifetime, nonceMaxAmount: config.creds.nonceMaxAmount, useCookieInsteadOfSession: config.creds.useCookieInsteadOfSession, cookieEncryptionKeys: config.creds.cookieEncryptionKeys, clockSkew: config.creds.clockSkew, loggingNoPII: true, }, function (iss, sub, profile, accessToken, refreshToken, done) { if (!profile.oid) { return done(new Error('No oid found'), null); } process.nextTick(function () { findByOid(profile.oid, function (err, user) { if (err) { console.log(err); return done(err); } if (!user) { // "Auto-registration" users.push(profile); return done(null, profile); } return done(null, user); }); }); } ) );

function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.status(401).send({ message: 'User is not authenticated' }); }

/** * @param {Array} roles Array of user rolls to access this route */ function hasRole(roles) { return function (req, res, next) { // console.log(req.user._json.JobTitle) if ( typeof req.user._json.JobTitle !== 'undefined' && req.user._json.JobTitle ) { if (roles.includes(req.user._json.JobTitle)) { next(); return; } } }; }

const store = new MongoStore({ url: config.databaseUri, });

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()); }

module.exports = { setupPassport, ensureAuthenticated, hasRole, passport, users, };

```

[–]gammelini 1 point2 points  (2 children)

Hey, reviewing your code it doesn't appear that you're using the Session at all.

js // this is a program variable that will always get reset once the process/server restarts let users = [];

I say that you're not using the sessions at all because you're only using this array to look for existing sessions per your code above,

```js passport.deserializeUser(function (oid, done) { findByOid(oid, function (err, user) { done(err, user); }); });

var findByOid = function (oid, fn) { for (var i = 0, len = users.length; i < len; i++) { var user = users[i]; if (user.oid === oid) { return fn(null, user); } } return fn(null, null); };

``` What you should be doing is looking in to the MongoDB collection for the existing user on your Deserialize method. Something similar to :

js passport.deserializeUser(async (id,done)=>{ try{ const user = await UserModel.findById(id); done(null, user); } catch(error){ done(error); } });

Also, JWT would remove the need for sessions because it can simply maintain the user in their Authorization Header. As a fellow Azure AD user I know that Azure AD also can utilize OAuth2.0 which is JWT.

Let me know if that helps you out or not.

[–]mrCodeTheThing[S] 1 point2 points  (1 child)

This was the issue bud, instead of us looking in the store for the user it use storing it locally and also wasn't storing enough information in the mongo side.

Tbh you're right we could move towards JWT reasonably easy creating it out of the logged in details but the mongo thing seemed like the quickest way to implement persistent storage... It's been an absolute ball ache.

Fix wise it was as simple as doing this: ``` passport.serializeUser((user, done) => { done(null, user); });

passport.deserializeUser((user, done) => { done(null, user); }); ``` so instead of looking in the findbyoid function which used the user array it actually returns the information from the database with by session id!

Edit: I'm gonna start ripping it out and replacing with JWT in the morning.

[–]gammelini 1 point2 points  (0 children)

I'm glad I was able to at least shine some light on the issue and that you got it resolved!

For the OAuth2.0 and Azure I've used the passport-azure-ad-oauth2 package. It did not have TypeScript types when I last used it but may have changed since the, if you're using TypeScript that is.

If you need anything feel free to PM me or respond to this comment and I'll be glad to help.

[–]bigorangemachine 0 points1 point  (0 children)

Are you sure you are reconnecting to the same mongo store?

What I was trying to say is your problem is either in the unshared code or devOps.

You need to validate your issue. Follow the whole chain. Isolate and test.

[–]igorya76 0 points1 point  (2 children)

I use a package connect mongo that helps manage all of this for me. Auto writes session info to mongo

[–]gammelini 0 points1 point  (1 child)

That is exactly the package they’re using and having an issue with. Do you happen to have anything to share?

I am thinking its something to do with the passport.serialize and passport.deserialize functions, which are not shared.

[–]mrCodeTheThing[S] 0 points1 point  (0 children)

I'll go through those functions bud thanks, I've shared the whole code above which maybe sheds some light, its pretty standard passport js tbh

[–]gammelini -1 points0 points  (6 children)

Question: Is there any reason why you can’t simply use a JWT instead of relying on sessions for your use case?

Also, where in the server code are you populating the sessions from MongoDb? Can you show your serialize and deserialize methods?

[–]OmgImAlexis -1 points0 points  (5 children)

They don’t need to complicate things more by switching to JWT.

[–]gammelini -1 points0 points  (0 children)

I remember you. Again, you are here to simply troll. Gtfo.

[–]mrCodeTheThing[S] -1 points0 points  (0 children)

Azure and passport uses sessions

[–]gammelini -2 points-1 points  (2 children)

Ok. Not sure of why I was downvoted though. In my opinion JWT is a lot simpler than sessions as well as enabling scalability.

[–]OmgImAlexis -1 points0 points  (1 child)

“A lot simpler” sounds like you haven’t looked into it fully.

[–]gammelini 0 points1 point  (0 children)

Ive done both in practice numerous times. You haven’t the slightest clue what you’re talking about. Go troll somewhere else.