« Previous Tutorial Next Tutorial »

Welcome back to Five Minute React! After a brief foray into optimization, it’s time to make our log in page log a user in. Well, time to get started anyway. There’s a lot of code to cover, here, and it’s going to take multiple tutorials before we can see anything really happening. I try as often as possible to end these tutorials by giving you something that actually works, but that’s probably not going to happen here. Sorry!

Let’s jump in. First off, we need to add two lines to make our new babel-friendly webpack config file work in development mode. So open up /app.js in Sublime and at the very top, add the following:

require('babel-register');

Now change line 17 from:

const webpackConfig = require('./webpack.config);

to:

const webpackConfig = require('./webpack.config.babel');

Save this file. Switch into a terminal window or command prompt. If your server’s still running in production mode, kill that. Then type the following:

yarn add babel-register whatwg-fetch

That second module adds a polyfill for fetch so that we can be sure it’ll work on lots of browsers (it should work without the fill on reasonably current versions of Chrome and FireFox, and maybe Microsoft Edge). You may not have heard the term “polyfill” before, but you’ve probably used one or two in your life. Polyfills are basically pieces of JavaScript that enable new features in older browsers that don’t support them natively. They do this by basically duplicating the feature in JavaScript that the browser does support. This particular polyfill is for the newish “fetch” specification, which is basically a built-in way for browsers to do AJAX requests without needing to install a framework like jQuery or write your own set of tools. We’re going to use it to send requests to our API. It’s easy to use, easy to read, and works really well with async / await.

Now that everything’s installed, we can fire up our development server with:

nodemon yarn start

We’re a ways away from actually sending requests to our API yet. We’ve got code to write. Let’s do it! Switch over to Sublime and open up /routes/api/authentication.js … we’re making one quick tweak to our API due to a quirk in Passport, the module we use to authenticate users. Passport is built to take a username and a password. This username can be an email, or any other value, but you still have to call it “username” which is confusing. Also if you do that, it locks you into email-only logins, rather than supporting username OR email logins. We want to have the latter available (though we’re not going to set it up right now), and we also don’t want to have a variable called “username” which contains an email instead of, well, a username.

So our API’s going to have to do a bit of trickery. When we submit our login form, which will contain an email and a password, we’re going to look up the user associated with the email address, get their username, and submit that to Passport, along with the password, instead of the email address.

Below line 1, add the following code:

const mongoose = require('mongoose');

Below line 6, add an extra blank line for some breathing room, and then add this:

// configure mongoose promises
mongoose.Promise = global.Promise;

This will prevent some warnings from Mongoose about using its deprecated, built-in promise system. You could use any number of promise libraries here, but the default one’s fine for our purposes and doesn’t require us to load any further modules, so just go with that.

Now swing down to the top of our login block, which should be line 27 if your spacing matches mine. We need to make this function work with async await, so the first step is to add “async” to line 28, like this:

router.post('/login', async (req, res) => {

Just under that line, add the following:

  // look up the user by their email
  const query = User.findOne({ email: req.body.email });
  const foundUser = await query.exec();

  // if they exist, they'll have a username, so add that to our body
  if (foundUser) { req.body.username = foundUser.username; }

As you can see, we’re awaiting our database query and then going ahead and adding the username to our login packet before sending it to passport … if it exists. If the user lookup failed, no big thing! No username will be added to passport, and the login attempt will fail, as it should. We’ll display a generic “login incorrect” error when that happens. You never want to alert a user specifically that an email or username was actually found but the password is wrong, because if the login attempt is coming from a hacker, that encourages them to continue trying passwords for what they now know is an existing account.

All right, we’re set here. Save this file, and then create a new file in /src/components/account called LoginPageContainer.jsx. This is where we’re going to handle the logic that actually talks to the API, while LoginPage will remain a dumb component that just receives props. We’ll need to “connect” our container to Redux in order to manipulate the Store, but we’ll get to that in the next tutorial.

I said in an earlier tutorial that we might end up using a module called “Redux-thunk” to help with asynchronous actions. As it turns out, yes, we’re going to get there … but not yet. First we’re going to put our logic in our container, then in a couple of tutorials I’ll show you how to get async actions working (including explaining what thunks are), and move the connection to the API from component file to action file. This is a bit cleaner, especially in large applications.

Remember how I told you this tutorial was going to end with nothing really working yet? Yeah, sorry again … we’re getting close to the five minute mark, and the next step would take us into eight or nine minutes, so let’s stop here. We’ll fill in LoginPageContainer.jsx and make some changes to LoginPage.jsx in our next tutorial. That’ll get us to a point where we can see something actually happening! Then in the following tutorial, we’ll handle actually doing something once a successful login attempt occurs.

See you then!

« Previous Tutorial Next Tutorial »