Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passport: Allow sign up with name and email address? (Local Strategy)

Tags:

Is there any way to allow a user to register on the local strategy with his password, email and name?
Every example I could find online only use name/password or email/password.

I also searched through the the whole passport documentation, but that documentation isn't helpful at all. It's just one bloated site full of examples.
I just need an list of functions, classes and variables passport uses with explanations what they and every parameter of them do. Every good library has something like that, why can't I find it for passport?

Here are the key parts of my code:

passport.use('local-signup', new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password',
    //are there other options?
    //emailField did not seem to do anything
    passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
    //check if email not already in database
        //create new user using "email" and "password"
        //I want an additional parameter here "name"
}));

So is passport really that limited? There has to be a way to do this, right?

like image 718
Forivin Avatar asked Sep 04 '15 12:09

Forivin


People also ask

What is local strategy in passport?

passport-local is the strategy you would use if you are authenticating against a username and password stored 'locally' i.e. in the database of your app - 'local' means local to your application server, not local to the end user.

Can passport use multiple strategies?

Passport's middleware is built in a way that allows you to use multiple strategies in one passport.

What is Passport login system?

Passport is a popular, modular authentication middleware for Node. js applications. With it, authentication can be easily integrated into any Node- and Express-based app. The Passport library provides more than 500 authentication mechanisms, including OAuth, JWT, and simple username and password based authentication.


2 Answers

You can be a little confused but passport doesn't implement signup methods. It's just authorisation library. So you must handle that use-case on your own.

First of all, create route that will be responsible for sign-up and your checks:

signup: function (req, res) {
  User
    .findOne({
      or: [{username: req.param('username')}, {email: req.param('email')}]
    })
    .then(function(user) {
      if (user) return {message: 'User already exists'};          
      return User.create(req.allParams());
    })
    .then(res.ok)
    .catch(res.negotiate);
}

The example above is based on Sails framework, but you can fit it with no problems to your own case.

Next step is include passport local strategy.

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

var LOCAL_STRATEGY_CONFIG = {
  usernameField: 'email',
  passwordField: 'password',
  session: false,
  passReqToCallback: true
};

function _onLocalStrategyAuth(req, email, password, next) {
  User
    .findOne(or: [{email: email}, {username: email}])
    .then(function (user) {
      if (!user) return next(null, null, {
        code: 'E_USER_NOT_FOUND',
        message: email + ' is not found',
        status: 401
      });

      if (!HashService.bcrypt.compareSync(password, user.password)) return next(null, null, {
        code: 'E_WRONG_PASSWORD',
        message: 'Password is wrong',
        status: 401
      });

      return next(null, user, {});
    })
    .catch(next);
}

passport.use(new LocalStrategy(LOCAL_STRATEGY_CONFIG), _onLocalStrategyAuth));

We have only signin task now. It's simple.

signin: function(req, res) {
  passport.authenticate('local', function(error, user, info) {
    if (error || !user) return res.negotiate(Object.assign(error, info));
    return res.ok(user);
  })(req, res);
}

This way is more suitable for passport and works great for me.

like image 55
Eugene Obrezkov Avatar answered Oct 13 '22 00:10

Eugene Obrezkov


Say you have this

app.post('/login', urlencodedParser,
    // so, user has been to /loginpage and clicked submit.
    // /loginpage has a post form that goes to "/login".
    // hence you arrive here.
    passport.authenticate('my-simple-login-strategy', {
        failureRedirect: '/loginagain'
    }),
        function(req, res) {
            console.log("you are in ............")
            res.redirect('/stuff');
    });

Note that the .authenticate has an explicit tag.

The tags is 'my-simple-login-strategy'

That means you have this ...

passport.use(
    'my-simple-login-strategy',
    // !!!!!!!!!!!!!note!!!!!!!!!!, the DEFAULT there (if you have nothing)
    // is 'local'. A good example of defaults being silly :/
    new Strategy(
        STRAT_CONFIG,
        function(email, password, cb) {
           // must return cb(null, false) or cb(null, the_user_struct) or cb(err)
           db.findUserByEmailPass(email, password, function(err, userFoundByDB) {
                if (err) { return cb(err); }
                if (!userFoundByDB) { return cb(null, false); }
                console.log('... ' + JSON.stringify(userFoundByDB) )
                return cb(null, userFoundByDB)
           })
        }
    )
)

!!! !!! NOTE THAT 'local' IS JUST THE DEFAULT TAG NAME !!! !!!

In passport.use, we always put in an explicit tag. It is much clearer if you do so. Put in an explicit tag in the strategy and in the app.post when you use the strategy.

So that's my-simple-login-strategy.

What is the actual db.findUserByEmailPass sql function?

We'll come back to that!

So we have my-simple-login-strategy

Next ...... we need my-simple-createaccount-strategy

Note that we are still sneakily using passport.authenticate:

So:

the strategy my-simple-createaccount-strategy will actually make an account.

However .............

you should still return a struct.

Note that my-simple-login-strategy has to return a struct.

So, my-simple-createaccount-strategy also has to return a struct - in exactly the same way.

app.post('/createaccount', urlencodedParser,
    // so, user has been to /createanaccountform and clicked submit,
    // that sends a post to /createaccount. So we are here:
    passport.authenticate('my-simple-createaccount-strategy', {
        failureRedirect: '/loginagain'
    }),
        function(req, res) {
            console.log("you are in ............")
            res.redirect('/stuff');
    });

And here's the strategy ..........

passport.use(
    'my-simple-createaccount-strategy',
    new Strategy(
        STRAT_CONFIG,
        function(email, password, cb) {
            // return cb(null, false), or cb(null, the_user_struct) or cb(err)
            db.simpleCreate(email, password, function(err, trueOrFalse) {
                if (err) { return cb(err); }
                if (!trueOrFalse) { return cb(null, false); }
                return cb(null, trueOrFalse)
            })
        }
    )
)

The strategy is pretty much the same. But the db call is different.

So now let's look at the db calls.

Let's look at the db calls!

The ordinary db call for the ordinary strategy is going to look like this:

exports.findUserByEmailPass = function(email, password, cb) {
    // return the struct or false via the callback
    dc.query(
        'select * from users where email = ? and password = ?',
        [email, password],
        (error, users, fields) => {
            if (error) { throw error } // or something like cb(new Error('blah'));
            cb(null, (users.length == 1) ? users[0] : false)
        })
}

So that's exports.findUserByEmailPass, which is used by my-simple-login-strategy.

But what about exports.simpleCreate for my-simple-createaccount-strategy?

A simple toy version would

  1. check if the username exists already - return false at this point if it does exist already, then
  2. create it, and then
  3. actually just return the record again.

Recall that (3) is just like in the ordinary "find" call.

Remember ... the strategy my-simple-createaccount-strategy will actually make an account. But you should still return a struct in the same way as your ordinary authenticate strategy, my-simple-login-strategy.

So exports.simpleCreate is a simple chain of three calls:

exports.simpleCreate = function(email, password, cb) {
    // check if exists; insert; re-select and return it
    dc.query(
        'select * from users where email = ?', [email],
        (error, users, fields) => {
            if (error) { throw error } // or something like cb(new Error('blah'));
            if (users.length > 0) {
                return cb(null, false)
            }  
            else {
                return partTwo(email, password, cb)
            }
        })
}

partTwo = function(email, password, cb) {
    dc.query(
        'insert into users (email, password) values (?, ?)', [email, password],
        (error, users, fields) => {
            if (error) { throw error } // or something like cb(new Error('blah'));
            partThree(email, password, cb)
        })
}

partThree = function(email, password, cb) {
    dc.query(
        'select * from users where email = ? and password = ?', [email, password],
        (error, users, fields) => {
            if (error) { throw error } // or something like cb(new Error('blah'));
            cb(null, (users.length == 1) ? users[0] : false)
        })
}

And that all works.

But note that

passport has nothing to do with account creation!

In fact, you do not have to use a strategy at all.

In app.post('/createaccount' you can, if you wish, do nothing with passport.authenticate ... don't even mention it in the code. Don't use authenticate at all. Just go ahead and do the sql process of inserting a new user, right there in app.post.

However, if you "trickily" use a passport strategy - my-simple-createaccount-strategy in the example - you have the bonus that the user is then immediately logged-in with a session and everything works in the same pattern as the login post. Cool.

like image 45
Fattie Avatar answered Oct 13 '22 01:10

Fattie