Spencer FengSpencer Feng

Authentication in React Apps with JWT: Part 5

This is the fifth tutorial in our ‘Authentication and Authorisation in React Apps with JSON Web Tokens’ series. In this tutorial, we will work on the login process:

  • Create the route on the server side to handle user login
  • Create our Redux actions to update the app state after a user logged in
  • Create React components for the login form

Create the route on the server side to handle user login

Like what we did with user signup, we will utilise the ‘passport’ package to create the route to handle user login.

First, please find below the named strategy called ‘local-signin’ using passport LocalStrategy which is added to the ‘app.js’ file.

// create the 'local-signin' named strategy
passport.use('local-signin', new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password',
    passReqToCallback: true,
    session: false
},
function(req, username, password, done) {
    User.findOne({
        'email': username
    }, function(err, user) {
        if (err) {
            return done(err);
        }

        // if no user is found, return 
        if (!user) {
            return done(null, false);
        }

        // if the user is found, but the password is wrong, return
        if (! user.validPassword(password)) {
            return done(null, false);
        }

        // create a jwt
        const payload = {
            sub: user._id
        }
        const token = jwt.sign(payload, process.env.JWT_KEY);

        const data = {
            _id: user._id,
            email: user.email,
            firstName: user.firstName,
            lastName: user.lastName
        };

        return done(null, token, data);

    });
}));

It is very similar to the ‘local-signup’ named strategy which we created in the previously. The only difference is that it does not create a new user, instead, it finds the user with the provided username and password and creates the token.

Next, we create the route to handle the sign in request from the client site. Please add the code below to the ‘app.js’ file.

// create the route to handle user signin
app.post('/signin', function(req, res, next) {

    res.setHeader("Access-Control-Allow-Origin", "*");

    passport.authenticate('local-signin', function(err, token, userData) {

        if (err) {
            return res.status(400).json({
                success: false,
                message: err.message
            });
        }

        return res.json({
            success: true,
            message: 'You have succesfully signed in',
            token: token,
            user: userData
        });

    })(req, res, next);

});

Create our Redux actions to update the app state after a user logged in

Let’s create the ‘userLogin’ action in the ‘client/src/actions.js’ file:

export const userLogin = (user, history) => {
    return dispatch => {
        fetch('http://localhost:3000/signin', {
            method: 'post',
            mode: 'cors',
            headers: {
                "Content-type": "application/x-www-form-urlencoded; charset=UTF-8" 
            },
            body: `email=${user.email}&password=${user.password}`
        })
        .then(response => {
            if (response.status >= 200 && response.status < 300) {
                return Promise.resolve(response);
            } else {
                return Promise.reject(new Error(response.statusText));
            }
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);
            if (data.success === true) {
                // Save the token to local storage
                localStorage.setItem('token', data.token);

                // Update the store state
                dispatch(setAuthenticatedUser(data.user));

                // Redirect to user logged-in landing page
                history.push('/');
            }
        })
        .catch(err => {
            console.log(err);
        });
    }
}

Create React components for the sign in form

Let’s create the container component first. Create a file called ‘LoginPageContainer.js’ in the ‘client/src/components’ directory with the code below:

import React from 'react';
import { connect } from 'react-redux';
import { userLogin } from '../actions';
import LoginPage from './LoginPage';

const mapDispatchToProps = (dispatch) => ({
    signin: (user, history) => { 
        dispatch(userLogin(user, history)); 
    }
});

export default connect(null, mapDispatchToProps)(LoginPage);

Like the ‘SignupPage.js’, the existing ‘LoginPage.js’ will be the presentational component with some changes. Let’s replace the code in ‘client/src/components/LoginPage.js’ with the code below:

import React, { Component } from 'react';

class LoginPage extends Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(e) {
        e.preventDefault();

        console.log(`email: ${this.email.value}`);
        console.log(`password: ${this.password.value}`);

        const user = {
            email: this.email.value,
            password: this.password.value
        };

        this.props.signin(user, this.props.history);
    }

    render() {
        return (
            <div className="row">
                <div className="col col-md-3"></div>
                <div className="col col-md-6">
                    <form onSubmit={this.handleSubmit}>
                        <div className="form-group">
                            <label htmlFor="email">Email</label>
                            <input type="text" name="email" id="email" className="form-control" ref={(email) => {this.email = email;}} />
                        </div>
                        <div className="form-group">
                        <label htmlFor="password">Password</label>
                            <input type="password" name="password" id="password" className="form-control" ref={(password) => {this.password = password;}} />
                        </div>
                        <div className="form-group">
                            <input type="submit" value="Login" />
                        </div>
                    </form>
                </div>
                <div className="col col-md-3"></div>
            </div>
        );
    }
}

export default LoginPage;

After doing this, we need to go to the ‘client/src/App.js’ to replace:

import LoginPage from './components/LoginPage';

with

import LoginPage from './components/LoginPageContainer';

Now, if we submit the login form with the correct username and password, we can see that we are redirected to the home page and an item with the key ‘token’ has been created in our local Storage object.

In the next tutorial in this series, we will enable the app to display the correct menu items based on the user’s login status. Stay tuned.

View the source code for this tutorial on GitHub.

Please sign up to our Newsletter if you want to get notified when new tutorials are available.

×