Professional Development · Technology

MERN Authorization with Passport.js

Any web application of significant complexity is going to require user-level security. In this post, I walk through how to create authorization for users in a MERN (MongoDB, Express, React, and Node) stack web application.

Full code is available here: https://github.com/dikuw/mern-passport

This post assumes knowledge of React and node.js and just shows how to incorporate routes for authorization using Passport.js.

First I’ll start with the server routes. I grouped everything in to one controller, although this could probably be cleanly split between separate User and Authorization controllers. The router only has four (4) routes:

router.get('/', authController.getCurrentUser);
router.post('/register', authController.checkAlreadyRegistered, authController.registerUser, passport.authenticate('local'), authController.login);
router.post('/login', passport.authenticate('local'), authController.login);
router.post('/logout', authController.logoutUser);

router.get('/') is the route is that is fired on initial app load and any home page reload. getCurrentUser looks like this:

exports.getCurrentUser = (req, res) => {
  console.log('current user: ', req.user);
  if (req.user) {
    res.json({ user: req.user });
  } else {
    res.json({ user: null });
  };
};

All this is doing is checking if there is a user on the route. If so, the route returns the user so the front end can set the state. It looks like this:

  getUser = () => {
    axios.get('/user/').then(response => {
      if (response.data.user) {
        this.setState({
          isLoggedIn: true,
          username: response.data.user.username
        });
      } else {
        this.setState({
          isLoggedIn: false,
          username: null
        });
      }
    });
  }

req.user is persisted in a passport session, which is why this works any time “/” is revisited.

Next we have router.post('/register') which is the most complex route with four distinct functions. Let’s look at each in turn.

authController.checkAlreadyRegistered verified the username is not already in the database. It looks like this:

exports.checkAlreadyRegistered = async (req, res, next) => {
  const { username } = req.body;
  const registered = await User.find({ username });
  if (registered[0] && registered[0]._id) {
    res.json({ error: `Sorry, already a user with the username: ${username}` });
    return;
  }
  next();
}

If the user is not already registered, the route continues to authController.registerUser to create the new user in the database, which looks like

exports.registerUser = async (req, res, next) => {
  const { username, password } = req.body;
  await (new User({ username, password })).save();
  next();
};

Then we run passport.authenticate('local') to create the session. More on this below. And finally we log in the newly registered user with authController.login:

exports.login = (req, res) => {
  req.login(req.user, function(err) {
    if (err) { res.json({ error: err }); }
    return res.send(req.user);
  });
};

req.login() is one of the functions that Passport.js exposes on the request object. Another one is req.logout, which we’ll see below.

On the front end, we handle the req.user object as follows:

login = async () => {
    const { username, password } = this.state;
    let response = await axios.post('/user/login/', { username, password });
    if (response.status === 200) {
      this.setState({ isLoggedIn: true });
      this.props.history.push("/");
    } else {
      console.log('login error');
    }
  }

The final route is router.post('/logout') which is one simple function, using another passport.js method:

exports.logout = (req, res) => {
  if (req.user) {
    req.logout();
    res.send({ msg: 'logging out' });
  } else {
    res.send({ msg: 'no user to log out' })
  };
};

Logout is handled on the front end as so:

logout = () => {
    axios.post('/user/logout').then(response => {
      if (response.status === 200) {
        this.setState({
          isLoggedIn: false,
          username: null,
          password: null
        });
        this.props.history.push("/");
      }
    }).catch(error => {
      console.log('logout error', error);
    });
  }

This code can be improved to alert the user when certain calls fail (for instance, on checkAlreadyRegistered or if you use the wrong password.

Any other changes?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s