5
votes

I have read several articles explaining how the passport authentication flows and understood most of the concepts. However, there are still a couple of vague points to be explained so that I can wrap my head around Passport once and for all.
Let's see this simple example that implements user registration:

passport.js

passport.use(
  'register',
  new LocalStrategy(
    {
      usernameField: 'username',
      passwordField: 'password',
      passReqToCallback: true,
      session: false,
    },
    (req, username, password, done) => {
      // TODO:Why is req.body.email is used and not req.body.username
      // And how are these values passed to register in the first place?
      console.log(username);
      console.log(req.body.email);

      try {
        User.findOne({
          where: {
            [Op.or]: [
              {
                username,
              },
              { email: req.body.email },
            ],
          },
        }).then(user => {
          if (user != null) {
            console.log('username or email already taken');
            return done(null, false, {
              message: 'username or email already taken',
            });
          }
          /**
           * on register the user’s password is hashed and salted with the encryption package bcrypt
           * 
           */
          bcrypt.hash(password, BCRYPT_SALT_ROUNDS).then(hashedPassword => {
            User.create({
              username,
              password: hashedPassword,
              email: req.body.email,
            }).then(user => {
              console.log('user created');
              return done(null, user);
            });
          });
        });
      } catch (err) {
        //In case of an Error interacting with our database, we need to invoke done(err)
        //Calling done will make the flow jump back into passport.authenticate. 
        //It's passed the error, user and additional info object (if defined).
        return done(err);
      }
    },
  ),
);

registerUser.js:

app.post('/registerUser', (req, res, next) => {
    //Calling done will make the flow jump back into passport.authenticate. 
    //It's passed the error, user and additional info object (if defined).

    passport.authenticate('register', (err, user, info) => {
      if (err) {
        console.error(err);
      }
      if (info !== undefined) {
        console.error(info.message);
        res.status(403).send(info.message);
      } else {
        // eslint-disable-next-line no-unused-vars
        req.logIn(user, error => {
          console.log(user);
          const data = {
            first_name: req.body.first_name,
            last_name: req.body.last_name,
            email: req.body.email,
            username: user.username,
          };
          console.log(data);
          User.findOne({
            where: {
              username: data.username,
            },
          }).then(user => {
            console.log(user);
            user
              .update({
                first_name: data.first_name,
                last_name: data.last_name,
                email: data.email,
              })
              .then(() => {
                console.log('user created in db');
                res.status(200).send({ message: 'user created' });
              });
          });
        });
      }
    })(req, res, next);
  });

Question 1: I do not see how the code inside LocalStrategy has access to the user information knowing that Passport.authenticate has been called in this manner:

    app.post('/registerUser', (req, res, next) => {
passport.authenticate('register', (err, user, info) => {

So how does the code inside **regiser LocalStrategy ** accesses the username,email and password:

(req, username, password, done) => {
  console.log(username);
  console.log(req.body.email);  

Question2: How come username inside LocalStrategy is called username directly (the same thing for password) and email is called by req.body.email?

 console.log(username);
  console.log(req.body.email); 

and here:

User.create({
              username,
              password: hashedPassword,
              email: req.body.email,
            })

Question 3: Why is there a need to update the user information in the request callback if the user has already been created in the register LocalStrategy:

registerUser.js

  .update({
                first_name: data.first_name,
                last_name: data.last_name,
                email: data.email,
              })

passport.js

User.create({
              username,
              password: hashedPassword,
              email: req.body.email,
            })

EDIT 1
Question 4: What is the role of (req, res, next); at the end of the POST callback?

2

2 Answers

0
votes

For question 1: when you are calling passport.authenticate('register') you should pass req, res, and next at then end, so that passport.use(localStrategy) gets the user information.

For question 2, username getting from the req object inside localstrategy

For question 3: You don't need to update the user info.instead you can directly send the res to user in .then() function as you did:

then(() => {
  console.log('user created in db');
  res.status(200).send({
    message: 'user created'
  });
});
0
votes

Question 1&2: The answer lies within the object that is passed LocalStrategy:

   {
      usernameField: 'username',
      passwordField: 'password',
      passReqToCallback: true,
      session: false,
    }  

The attribute passReqToCallback, the req will be passed as the first argument to the verify callback:

(req, username, password, done) => {}  

However, what is still not clear is how the username and password are passed to this function. Why are they defined in this manner in this object:

 usernameField: 'username',
 passwordField: 'password', 

Shouldn't they be accessed through req.body.username just like the email:

User.findOne({
          where: {
            [Op.or]: [
              {
                username,
              },
              { email: req.body.email },
            ],
          },   

Question 3: Here's an explanation I found:

I could have passed this extra data through to the middleware as well, but I want Passport to only handle authentication, not user creation as well. Modularization, remember.

Plus, if the authentication were to be split out into a separate service with a separate database of just encrypted usernames and passwords, this would make it easier to do so, then use the username or ID to find and update the corresponding user record in this registration service.