Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple "local" strategies #50

Closed
tiagojsag opened this issue Aug 7, 2012 · 14 comments

Comments

@tiagojsag
Copy link

@tiagojsag tiagojsag commented Aug 7, 2012

Hi

This is not so much a bug, but rather a question on if something can be achieved using the current implementation. Also, I'm not very proficient in passport or node, so please pardon me if I say something stupid.

Anyway, I'm trying to implement an kind of online "mini shopping mall", using express, sequelize over mysql and, ofc, passport. As usual, there's a table for customers but also a table for the several companies selling their stuff on the site.

My idea, if possible, is to have 2 LocalStrategies, one for each of the above mentioned user types. Of course, customers and sellers will have two completely different interfaces, so I was wondering if it possible to have two LocalStrategies plugged into passport, and then validate authentication based on each of them (so that if a customer tries to access the companies' backoffice pages, they get denied access).

I've checked another bug report talking about multiple strategies, but it seems to me that, once a user is logged in, passport just cares that he is authenticated, not the method he used. Also, i was left with the idea that strategies are accessed by identifier, so using two of the same type would be tricky/not possible.

Thanks in advance, and congratulations on your great work on passport
cheers

@jaredhanson

This comment has been minimized.

Copy link
Owner

@jaredhanson jaredhanson commented Aug 7, 2012

Yes, it's possible. You can use named strategies for this, as follows:

// use two LocalStrategies, registered under user and company names
passport.use('user', new LocalStrategy(
  function(username, password, done) {
    User.findOne(/* ... */)
  }
));

passport.use('company', new LocalStrategy(
  function(username, password, done) {
    Company.findOne(/* ... */)
  }
));


// authenticate using local strategies, depending on whether the route is the user or company interface
app.post('/user/login', 
  passport.authenticate('user', { successRedirect: '/user/home', failureRedirect: '/user/login' }));

app.post('/company/login', 
  passport.authenticate('company', { successRedirect: '/company/home', failureRedirect: '/company/login' }));

Does that answer your question?

@tiagojsag

This comment has been minimized.

Copy link
Author

@tiagojsag tiagojsag commented Aug 7, 2012

Wow, really fast answer, thanks ;)
Only one detail missing: is there any way to prevent a valid user with valid credentials to authenticate on /user/login and, after authenticated, accessing /company/home? The examples i saw using req.isAuthenticated() don't seem to take into account what specific credentials the user has.

@jaredhanson

This comment has been minimized.

Copy link
Owner

@jaredhanson jaredhanson commented Aug 7, 2012

That falls into the category of access control, which is not something Passport deals with. This can be as simple or complex as you make it, but I would start with something like the following:

function ensureOnlyCompany(req, res, next) {
  if (isCompany(req.user)) { return next(); }
  res.redirect('/login')
}

app.get('/company/page',
  ensureOnlyCompany,
  function(req, res) {
    // serve the company page
  });

Just mount some middleware before your routes, which gates companies and users, preventing them from accessing pages they are not allowed to see.

@jaredhanson jaredhanson closed this Aug 7, 2012
@tiagojsag

This comment has been minimized.

Copy link
Author

@tiagojsag tiagojsag commented Aug 8, 2012

Ok, that seems to take care of my problem ;) Thanks for your help!

@shazly

This comment has been minimized.

Copy link

@shazly shazly commented Sep 11, 2013

How can I serialize and deserialize different users using this approach (example mentioned above: user/company)?

@jaredhanson

This comment has been minimized.

Copy link
Owner

@jaredhanson jaredhanson commented Sep 11, 2013

passport.serializeUser(function(user, done) {
  if (isUser(user)) {
    // serialize user
  } else if (isCompany(user)) {
    // serialize company
  }
});
@shazly

This comment has been minimized.

Copy link

@shazly shazly commented Sep 11, 2013

I am using two Schemas: User and Company. I can't use this same approach in the deserializeUser method, bearing in mind that it takes id as its parameter and not user..so I can't use isUser/isCompany.

@jaredhanson

This comment has been minimized.

Copy link
Owner

@jaredhanson jaredhanson commented Sep 12, 2013

I recommend reading, lots of experimentation, and posting your questions to a forum such as Stack Overflow.

This is the place to discuss issues with the module, and I simply don't have time to provide further guidance beyond what I already have.

Cheers,
Jared

@siegelty

This comment has been minimized.

Copy link

@siegelty siegelty commented Sep 4, 2015

@shazly we have figured out a work around for this (sorry that it is two years too late!). What we did was in our serialize function, we used the if-then statement described above. If it was a customer, we would take their ID and prepend a "a" in front of it. For example, a customer with an id of 112 would have a serialization of a112. If it was a company, we would prepend a "b" in front of their ID. So if a company had an ID of 112, it would be b112.

In our schemas, the ID's are primary keys, so no two companies will have the same ID, nor would customers have the same ID. With this prepending of an "a" or "b" allows us to ensure each serialized ID will be unique.

This also allows us to deserialize differently based on the ID. At the start of the deserialize function, we took in the id parameter and did an if-then statement for id.charAt(0). If it was an "a" we did one thing, and if it was a "b" we did another thing.

Further, to do the database calls with an id, we did id.substr(1), to get only the id.

Hope this helps!

@kyle-rader

This comment has been minimized.

Copy link

@kyle-rader kyle-rader commented Jan 29, 2016

@shazly That's a great work around! Thank you so much for posting! I will try implementing this now. I'm adding a second type of user to a website for an event and ran into both of the problems on this thread.

UPDATE: A much cleaner solution than trying to mess with the object id's is to simplify create a new key using the id field and some other indicator. I added a userType property to my users and now do the following instead of just using the user.id:

passport.serialzeUser(function(user, done) {
  var key = {
    id: user.id,
    type: user.userType
  }
  done(null, key);
}
passport.deserializeUser(function(key, done) {
  // this could be more complex with a switch or if statements
  var Model = key.type === 'type1' ? Model1 : Model2; 
  Model.findOne({
    _id: key.id
  }, '-salt -password', function(err, user) {
    done(err, user);
  }
}

Cheers!

@noviceC

This comment has been minimized.

Copy link

@noviceC noviceC commented Mar 5, 2016

Hi
I have created a login medium using passportjs with mongodb, it works fine. But, now i want is to redirect pages according to the user type, i have two types of users: employee and customer. I have used only one schema in mongodb.. when registering a new user at that time only the user type is decided that the new user would be an employee or customer. I am new to nodejs.. i am confused about how i can achieve it. Please suggest.

@sreenathe12

This comment has been minimized.

Copy link

@sreenathe12 sreenathe12 commented Mar 2, 2017

I got same problem @noviceC....did you any solutions?

@mckagabo

This comment has been minimized.

Copy link

@mckagabo mckagabo commented Jun 13, 2018

@kyle-rader your solution just worked for me now in 2018 thank you very much it saved my day

@matthewvolk

This comment has been minimized.

Copy link

@matthewvolk matthewvolk commented Jul 8, 2018

I found another solution similar to the one that @kyle-rader offered, copying code from this link below with more descriptive naming conventions. Credit to Matthew Payne:

https://stackoverflow.com/a/24336272/8211101

function SessionConstructor(userId, userGroup, details) {
    this.userId = userId;
    this.userGroup = userGroup;
    this.details = details;
}

passport.serializeUser(function (userObject, done) {
    // userObject could be a Model1 or a Model2... or Model3, Model4, etc.
    let userGroup = "model1";
    let userPrototype =  Object.getPrototypeOf(userObject);
  
    if (userPrototype === Model1.prototype) {
        userGroup = "model1";
    } else if (userPrototype === Resident.prototype) {
        userGroup = "model2";
    } 

    let sessionConstructor = new SessionConstructor(userObject.id, userGroup, '');
    done(null,sessionConstructor);
});

passport.deserializeUser(function (sessionConstructor, done) {

    if (sessionConstructor.userGroup == 'model1') {
        Model1.findOne({
            _id: sessionConstructor.userId
        }, '-localStrategy.password', function (err, user) { // When using string syntax, prefixing a path with - will flag that path as excluded.
            done(err, user);
        });
    } else if (sessionConstructor.userGroup == 'model2') {
        Model2.findOne({
            _id: sessionConstructor.userId
        }, '-localStrategy.password', function (err, user) { // When using string syntax, prefixing a path with - will flag that path as excluded.
            done(err, user);
        });
    } 

});

What you need to replace in the code above to integrate this with your application:

  1. "model1" and "model2" <= Unique identifiers used to add uniqueness to the session constructor
  2. "Model1" and "Model2" <= Your two different user models
  3. localStrategy.password <= Replace this with the object access key of your password and other sensitive info. See [projection] parameter of http://mongoosejs.com/docs/api.html#find_find (do not remove the single quotes around this value or the hyphen prefixed to this value from the code below)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.