Spencer FengSpencer Feng

Authentication in React Apps with JWT: Part 4

This is the fourth part of our ‘Authentication and Authorisation in React Apps with JSON Web Tokens’ series. In this part, we will work on the signup process:

  • Create the route on the server side to handle user signup
  • Create our Redux actions to update the app state after a user signed up
  • Create React components for the signup form

Create the route on the server side to handle user signup

We will utilise the ‘passport’ package to create the endpoint on the server to handle user signup.

First, we will create a named strategy called ‘local-signup’ using passport LocalStrategy.

// create the 'local-signup' named strategy
passport.use('local-signup', 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 (user) {
            return done(null, false);
        }

        let newUser = new User();

        newUser.email = username;
        newUser.password = newUser.generateHash(password);
        newUser.firstName = req.body.firstName;
        newUser.lastName = req.body.lastName;

        newUser.save(function(err) {
            if (err) {
                throw err;
            }

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

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

            return done(null, token, data);
        });

    });
}));

By default, passport LocalStrategy expects to find credentials in parameters named username and password, but in our application, we would like to use email instead of username, so I specified the username field explicitly in line 32 when I created the LocalStrategy instance. In addition, our signup form also contains ‘firstName’ and ‘lastName’ fields and we need to get the values of these 2 fields from the request body, so that is why we set ‘passReqToCallback’ true.

After we get the request, email and password, we check if a user with the same email already exists in the system. If yes, we return immediately with an error. If not, we will create the user and save the user to our database.

After the user is saved to the database successfully, we create the JSON Web Token using the package ‘jsonwebtoken’ which we installed in the previous post. In order to use it, we need to import the ‘jsonwebtoken’ package to the ‘app.js’ file.

var jwt = require('jsonwebtoken');

Please note, we store the key in the .env, so please do remember to include the ‘dotenv’ package at the top of the file.

require('dotenv').config();

The named strategy ‘local-signup’ has been created, so now our work is to create the route to handle the signup request sent from the client side. Please add the code below to the ‘app.js’ file.

app.post('/signup', function(req, res, next) {

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

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

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

        return res.json({
            success: true,
            message: 'You have successfully signed up and logged in',
            token: token,
            user: userData
        });
    })(req, res, next);
});

So, here’s the route handling the user signup request. There are a few things going on that are worth going over.

First, since our client side (the React app) has a different port than our server (the Express app), when it makes a request to the server, it is a cross-origin HTTP request. In order to give our client side app the permission to get the resources it needs, we need to set the ‘Access-Control-Allow-Origin’ header in our server’s response header. In our case, I set it to allow all domains.

Next, the callback function in the authenticate() method get called if authentication was successful.

Last, in the JSON response, we returned the token and the userData which contains the id, email, firstName and lastName of the User object.

Create our Redux actions to update the app state after a user signed up

Actions are payloads of information that send data from the application to the store. The reducer we created previously uses the data to update the application state. The action which we use to send data to the store when a user signs up needs to be an asynchronous action because we do not want to send the data to the store until we receive an answer from the server after we sent the request to the server.

We will use the Redux Thunk middleware to implement the asynchronous action for the signup event. Let’s install the redux-thunk package first:

$ npm install redux-thunk --save

After installing the redux-thunk package, we need to import it to our ‘client/src/index.js’:

import thunkMiddleware from 'redux-thunk';

Then we need to specify the middleware which is Redux Thunk middleware in our case when we create the Redux store in ‘client/src/index.js’, so we need to replace the code which we use to create the Redux store with the code below:

// Create the store
const store = createStore(combineReducers({
    cUser: reducers.cUser
}),
applyMiddleware(
    thunkMiddleware
));

Now, we are ready to create the actions. Let’s create a file ‘client/src/actions.js’ with the code below:

export const userSignup = (user, history) => {
    return dispatch => {
        fetch('http://localhost:3000/signup', {
            method: 'post',
            mode: 'cors',
            headers: {
                "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"  
            },
            body: `firstName=${user.firstName}&lastName=${user.lastName}&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 => {
            return response.json();
        })
        .then(data => {
            if (data.token !== false) {
                console.log(`response token: ${data.token}`);

                // Save the token to local storage
                localStorage.setItem('token', data.token);

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

                // Redirect to home page
                history.push('/');
            } else {
                // TODO
            }
        })
        .catch(err => {
            console.log(err);  
        });
    }
};

export const setAuthenticatedUser = (user) => ({ type: 'SET_USER', data: user });

As you can see, we created 2 actions here. The first action is userSignup and it is an asynchronous action which makes a request to our server and after it gets a successful response from the server, it dispatches a normal action which is setAuthenticatedUser in our case to update the application state. In the userSignup action, we also store the JWT token in localStorage in our browser, so that when a logged in user refreshes the web page before the token expires, the user can still be authenticated without having to login again.

Create React components for the signup form

We already created the React component which is ‘client/src/components/SignupPage.js’ to display the page with the signup form. But what we need is not just displaying the form, we also need to pass the action ‘userSignup’ we just created to it form the store as a prop of the React component. In order to achieve it, we need to divide our SignupPage component into a presentational component which is responsible for the how our signup page looks like (markup, styles) and a container component which is responsible for how our signup page works (data fetching, state updates).

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

import React from 'react';
import { connect } from 'react-redux';
import { userSignup } from '../actions';
import SignUpPage from './SignupPage';

const mapDispatchToProps = (dispatch) => ({
    signup: (user, history) => {
        dispatch(userSignup(user, history));
    }
});

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

What the code above does is that it allows us to pass the function which dispatches the userSignup action to the presentational component of signup page as a React component prop called ‘signup’.

Our existing ‘SignupPage.js’ will be the presentational component, but we need to make some changes to it, so that it can use the props that passed to it.

Let’s replace the code in ‘client/src/components/SignupPage.js’ with the code below:

import React, { Component } from 'react';

class SignupPage 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 = {
            firstName: this.firstName.value,
            lastName: this.lastName.value,
            email: this.email.value,
            password: this.password.value
        };

        this.props.signup(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="firstName">First Name</label>
                            <input type="text" name="firstName" id="firstName" className="form-control" ref={(firstName) => {this.firstName = firstName;}} />
                        </div>
                        <div className="form-group">
                            <label htmlFor="lastName">Last Name</label>
                            <input type="text" name="lastName" id="lastName" className="form-control" ref={(lastName) => {this.lastName = lastName;}} />
                        </div>
                        <div className="form-group">
                            <label htmlFor="email">Email</label>
                            <input type="email" 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="Sign Up" />
                        </div>
                    </form>
                </div>
                <div className="col col-md-3"></div>
            </div>
        );
    }
}

export default SignupPage;

Basically, the updated code allows the signup page presentational component to do following things:

  1. When the signup form is submitted, it gets the value of all the fields in the form
  2. Then it creates an object with the form values
  3. Then it calls the function that was passed to it as a prop to dispatch the ‘userSignup’ action.

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

import SignupPage from './components/SignuPage';

with

import SignupPage from './components/SignupPageContainer';

Now, if we submit the signup form in the signup page, we can see that a new user has been created in our database and an item with the key ‘token’ has been created in our local Storage object.

In the next tutorial in this series, we will work on the sign in process. See you then.

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.

×