Here we are at the last part of the Retrogames Archive tutorial. We have now a full working app written in JavaScript and it's pretty much what we were looking to achieve.

However, at present all the users have full access to the common operations on the archive: They can view, create and delete games entries.

In this last part of the tutorial we want to limit the operations of creating and deleting a game to allow only Authenticated user to perform them. Plus, we are going to improve the UI with friendly notifications.

The project is always available on my github, the master branch contains the complete code. Don't forget to copy the up-to-date css in your project!

Prerequisites

The only prerequisite for this very last part of the tutorial is familiarity with JSON web tokens. I take for granted all the other prerequisites described in the previous tutorials, part1 and part2.

NB: Since we are going to edit/update existing files I am going to highlight the changes with multi-line comments:

/* 
 * The new code starts after this comment...
 */ 

Table of Contents

Folder Structure

That's the final folder structure:

 --app
 ----models
 ------game.js
 ------user.js
 ----routes
 ------game.js
 ------user.js
 --config
 ----index.js
 --client
 ----dist
 ------css
 --------style.css
 ------fonts
 --------PressStart2p.ttf
 ------index.html
 ------bundle.js
 ----src
 ------actions
 --------filestack.js
 --------games.js
 --------auth.js
 ------components
 --------About.jsx
 --------AddGamePanel.jsx
 --------Archive.jsx
 --------Contact.jsx
 --------Form.jsx
 --------Game.jsx
 --------GamesListManager.jsx
 --------Home.jsx
 --------index.js
 --------Login.jsx
 --------Modal.jsx
 --------Signup.jsx
 --------Welcome.jsx
 ------constants
 --------auth.js
 --------filestack.js
 --------games.js
 ------containers
 --------AddGameContainer.jsx
 --------GamesContainer.jsx
 --------reducers
 ----------auth.js
 ----------filestack.js
 ----------games.js
 ----------index.js
 ----------routing.js
 --------sagas
 ----------auth.js
 ----------filestack.js
 ----------games.js
 ----------index.js
 ------index.js
 ------routes.js
 ------store.js
 ------utils.js
 --.babelrc
 --package.json
 --server.js
 --webpack-loaders.js
 --webpack-paths.js
 --webpack.config.js
 --yarn.lock

If you compare this folder structure with the last one you should notice we mostly added files to handle the user authentication on both the server side (new routes and a model) and the client side (new components, reducers, sagas, actions etc.).

Authentication

Authentication is handled on both parts of your application, the server and the client, so let's start with the server.

Server and JSON Token

We are using JSON web tokens to add a security layer to the app. Users should be able to sign up or login to modify the games archive.

First of all, open server.js and add two new routes:

import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import morgan from 'morgan';

import { getGames, getGame, postGame, deleteGame } from './app/routes/game';
// New routes and middleware to manage the authentication
import { signup, login, verifyAuth } from './app/routes/user';

const app = express();
const port = process.env.PORT || 8080;

const options = {
  server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
  replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } }
};
mongoose.Promise = global.Promise;
mongoose.connect(YOUR_MONGODB_URL', options);

const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));

app.use(bodyParser.urlencoded({ extended: true}));
app.use(bodyParser.json());
app.use(morgan('dev'));

app.use(express.static(__dirname + '/client/dist'));

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE');
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, x-access-token");
  next();
});

/*
 * New routes to handle Authentication
 */
app.post('/auth/login', login);
app.post('/auth/signup', signup);

app.route('/games')
    // verifyAuth is the security middleware to check the authentication
    .post(verifyAuth, postGame)
    .get(getGames);
app.route('/games/:id')
    .get(getGame)
    // Again delete requests pass through the security middleware
    .delete(verifyAuth, deleteGame);

app.route("*").get((req, res) => {
    res.sendFile('client/dist/index.html', { root: __dirname });
});

app.listen(port);

console.log(`listening on port ${port}`);
  • In the server main file we included two new routes in charge of handling the authentication: A POST requests to /auth/signup inserts a new user into the database and return the token while /auth/login checks the credentials and returns a token as well.
  • In addition, the route for posting a new game and the route for deleting a game are protected by verifyAuth middleware. Whenever the server receives a request to one of those routes, it first retrieves the token from the request header and verify it. Then, if the verification succeed, a call to next runs the function in charge of posting or deleting a game, otherwise the server simply returns a HTTP 403 forbidden status.

User Model

We now need to create the user model to store users in the database so we can go very simple: We need e-mail, password and name fields. In particular, the password will be hashed and to do so we let's add bcryptjs, a small library to help hashing passwords:

yarn add bcryptjs

Let's add a new fileuser.js in /app/models and paste the following code:

// Require some dependencies
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcryptjs');

// Our schema defines 3 fields, notice email must be unique
var userSchema = new mongoose.Schema({
  email: { type: String, unique: true, lowercase: true },
  password: { type: String, select: false },
  name: String
});

userSchema.pre('save', function (next) {
  var user = this;
  // before saving a hashed version of the password is created and saved into the db
  bcrypt.genSalt(10, function (err, salt) {
    bcrypt.hash(user.password, salt, function (err, hash) {
      user.password = hash;
      next();
    });
  });
});

// This utility function comes handy during authentication
userSchema.methods.comparePwd = function(password, done) {
  // Compare the password sent by the user with the one stored in the db
  bcrypt.compare(password, this.password, (err, isMatch) => {
    done(err, isMatch);
  });
};

// Export the model
module.exports = mongoose.model('User', userSchema);

Authentication Routes and Middleware

The logic to authenticate the user is also in charge to create the token to be sent as response: To create the token we can define payload with some useful information:

  1. sub: The subject is the user name. In the client the token can be decoded and the user name shown in the app along with a welcome message.
  2. exp: The expiration date is set to 1 day and to easily set it we are gonna install moment, very useful JavaScript library to work with dates.

So let's install jsonwebtoken and moment:

yarn add jsonwebtoken moment

In server.js we defined two new routes and a middleware all residing in the same file /app/routes/user.js. Let's create it and past the following code:

// Our new dependencies
import jwt from 'jsonwebtoken';
import moment from 'moment';
// We import the User model we have just defined
import User from '../models/user';
// The config file contains the secret to sign the token
import config from '../../config';

// Utility function to create and return the token, it requires TOKEN_SECRET from config
const createToken = name => {
  var payload = {
    sub: name,
    exp: moment().add(1, 'day').unix()
  };
  return jwt.sign(payload, config.TOKEN_SECRET);
}

// signup function for the /auth/signup route
const signup = (req, res) => {
  // query the database to make sure the e-mail is not taken already
  User.findOne({ email: req.body.email }, (err, existingUser) => {
    if (existingUser) {
    // HTTP 409 status is sent in case the e-mail is taken
      return res.status(409).json({ message: 'Email is already taken' });
    }

    // A new user is created with the information sent by the client
    const user = Object.assign(new User(), req.body);
    user.save((err, result) => {
      if (err) {
        res.send(err);
      }
      // Notice we also send the token as we want the user to be immediately logged in
      res.json({
        message: 'Welcome to Retrogames, you are now logged in',
        token: createToken(result.name)
      });
    });
  });
};

// Login function for /auth/login
const login = (req, res) => {
  // Query the database for user with that specific e-mail
  User.findOne({ email: req.body.email }, '+password', (err, user) => {
    if (!user) {
    // If the user doesn't exist just send a HTTP 401 status
      return res.status(401).json({ message: 'Invalid email/password' });
    }
    /* If the user exists, the password sent by the client is compared with the one in the db
    with the utilily function comparePwd
   */
    user.comparePwd(req.body.password, (err, isMatch) => {
      if (!isMatch) {
    // In case of wrong password, we send another HTTP 401 status
        return res.status(401).send({ message: 'Invalid email/password' });
      }
      // Correct information from the client, a token is sent
      res.json({ message: 'You are now logged in', token: createToken(user.name) });
    });
  });
};

// verifyAuth middleware to protect post and delete routes
const verifyAuth = (req, res, next) => {
  // Get the token from the header x-access-token
  const token = req.headers['x-access-token'];
  if (token) {
    // Verifies the token and the expiration
    jwt.verify(token, config.TOKEN_SECRET, function(err, payload) {
      // If the verification fails it returns http status 403
      if (err) {
        return res.status(403).send({
          message: 'Failed to authenticate token.'
        });
      } else {
        // Goes to the next route since there are no errors
        next();
      }
    });
  } else {
    // Requests without token return http status 403
    return res.status(403).send({
        message: 'No token provided.'
    });
  }
};

// Export the functions for server.js
export {
  signup,
  login,
  verifyAuth
};

There are 4 functions we created and 3 of them are exported for further usage in server.js.

  • createToken: It's an utility function in charge to create and return a valid token.
  • signup: The function receives a new user's information to create a new user entry in the database and returns the token. Before that, it checks if the e-mail was taken by another user before.
  • login: Whenever a user try to authenticate to /auth/login, the function first retrieves the correct user from the database (given the e-mail) and then verify the password. If all goes right, it sends the token back to the client.
  • verifyAuth: This is the middleware in charge to protect the archive: Only authenticated user can create or delete games.

Take a look at createToken: To sign the token we need a secret string that we imported from a config file, let's create /config/index.js and paste the following code:

const TOKEN_SECRET = process.env.TOKEN_SECRET || 'YOUR_SECRET_STRING';

export default {
  TOKEN_SECRET
};

Replace YOUR_SECRET_STRING with a string of your choice.

The server side is done, let's test it with Postman!

Test the Server

The first thing we can do is to try to add a new game with no token in the header to verify whether the server actually refuses your request or not:

POST Request to /games

in Postman, send a new game to localhost:8080/games and you should receive a HTTP 403 status with message "No token provided.":

The middleware seems to be working fine! Let's create a user now:

POST Request to /auth/signup

We need to send an e-mail, name and password to the server at /auth/signup, let's see if it works:

As you can see we received a token, let's now use it for creating a new game!

Authenticated POST Request to /games

We copy the token in the header tab for the post request. Here is the result:

And the game was successfully created!

Finally, we want to simulate the login request.

POST Request to /auth/login

Let's login to the server by sending e-mail and password:

And this works too, we can now go working on the client-side!

Client

In the client-side of the app we want show the buttons to login, sign-up or logout so let's take a look at these two screenshots:

  1. The user is not authenticated:

  • The users can login or sign-up.
  • Since the user is not authenticated, he/she cannot add/delete any games.
  1. The user is authenticated:

  • The user is now authenticated so the buttons to login/sign-up don't show.
  • The user has now the right to add/delete a game. Notice on the top right the welcome message along with the logout button.

Finally, the views to login/sign-up are pretty straightforward:

Manage the Routing with Redux

Before writing the authentication logic, let's think about the steps to login/signup: Once the user clicks on the login/register button, the client communicates with the server and if all goes well it receives back the token. At this point, a redirection the user to /games should happen. I find react-router-redux package very useful because we can control the routing in the state and we have a handy action-creator push to dispatch actions to the reducer and change view.

Let's add it by running:

yarn add react-router-redux

To use the push action-creator we need to add a new middleware when configuring the store:

In /client/src/store.js paste the following code:

import {
  createStore,
  applyMiddleware,
  compose
} from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
import reducer from './reducers';
/* 
 * Here we imported the routerMiddleware
 */ 
import { routerMiddleware } from 'react-router-redux';
import { hashHistory } from 'react-router';

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  /*
   * It requires the app history as parameter
   */ 
  const routeMiddleware = routerMiddleware(hashHistory);
  const store = createStore(
    reducer,
    /* 
     * we add it right after sagaMiddleware 
     */ 
    applyMiddleware(sagaMiddleware, routeMiddleware)
  );
  sagaMiddleware.run(rootSaga);

  return store;
}
export default configureStore;

Since we are using Immutable for the state we need to define the routing reducer by ourselves as well as pass a selector to access the payload state and convert it to a JavaScript object.

NB: These steps are not necessary with a mutable state.

Let's create the reducer in /client/src/reducers/routing.js:

// This is a standard definition for the routing reducer
import Immutable from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';

const initialState = Immutable.fromJS({
  locationBeforeTransitions: null
});

export default (state = initialState, action) => {
  if (action.type === LOCATION_CHANGE) {
    return state.set('locationBeforeTransitions', action.payload);
  }

  return state;
};

This is pretty straightforward, there is just an action to interact with the reducer and it comes directly from react-router-redux. We also gotta edit the index.js in /client/src/reducers:

import { combineReducers } from 'redux-immutable';
import { reducer as form } from 'redux-form/immutable';
import games from './games';
import filestack from './filestack';
/* 
 * Here we imported the routing reducer
 */ 
import routing from './routing';

export default combineReducers({
  games,
  form,
  filestack,
  /* 
   * Combine routing as well
   */ 
  routing,
});

The last thing to do is to call syncHistoryWithStore to get the routing part of the state and convert it to an object.

In /client/src/routes.js paste the following code:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
import { Home, Archive, Welcome, About, Contact } from './components';
/* 
 * Here we imported syncHistoryWithStore
 */ 
import { syncHistoryWithStore } from 'react-router-redux';

const store = configureStore();
/* 
 * Sync navigation events with the store
 */
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});

const routes = (
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={Home}>
        <IndexRoute component={Welcome} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
      </Route>
      <Route path="/games" component={Archive}>
        <IndexRoute component={GamesContainer} />
        <Route path="add" component={AddGameContainer} />
      </Route>
    </Router>
  </Provider>
);

export default routes;

That's all, the navigation is now synchronized with the store and we can dispatch actions to change the views. If you are interested in digging more into redux and immutability I suggest you to take a look at redux-immutable documentation where it also further explains what we have just done.

Time to write the login.

User Login

Let's start by the view, create a Login component in /client/src/components/Login.jsx and paste the following code:

// We import a bunch of dependencies
import React, { PureComponent } from 'react';
import { Link } from 'react-router';
import { Field, reduxForm } from 'redux-form/immutable';
// Some action-creators we are going to write later
import * as authActionCreators from '../actions/auth';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

class Login extends PureComponent {
  login () {
    // dispatch action to the redux-saga
    this.props.authActions.loginUser(this.props.location.query.next || '/games');
  }

  render () {
    const { picture, uploadPicture } = this.props;
    return (
      <div className="row scrollable">
        <div className="col-md-offset-2 col-md-8">
          <div className="text-left">
            <Link to="/games" className="btn btn-info">Back</Link>
          </div>
       <div className="panel panel-default">
         <div className="panel-heading">
        <h2 className="panel-title text-center">Login</h2>
         </div>
         <div className="panel-body">
           <form>
                <div className="form-group text-left">
                  <label htmlFor="name">Name</label>
                  <Field
                    name="email"
                    type="text"
                    className="form-control"
                    component="input"
                    placeholder="Enter the name"
                  />
                </div>
                <div className="form-group text-left">
                  <label htmlFor="password">Password</label>
                  <Field
                    name="password"
                    component="textarea"
                    className="form-control"
                    placeholder="Enter the password"
                    rows="5"
                  />
                </div>
          <button 
            type="button" 
            className="btn btn-submit btn-block" 
            onClick={() => this.login()}
          >
          Login
          </button>
          </form>
        </div>
      </div>
       </div>
     </div>
    );
  }
}
// Bind the action-creators so that we can call them as props
function mapDispatchToProps (dispatch) {
  return {
    authActions: bindActionCreators(authActionCreators, dispatch)
  };
}
// Wrap the login into a reduxForm HOC
export default reduxForm({ form: 'login' })(connect(null, mapDispatchToProps)(Login));

The Login component is nothing but a redux-form asking for e-mail and password (remember the test with Postman?). The login button is responsible for calling the login function which dispatches the action described by loginUser.

Let's make the Login available through /client/src/components/index.js:

import About from './About';
import Contact from './Contact';
import Form from './Form';
import Game from './Game';
import GamesListManager from './GamesListManager';
import Home from './Home';
import Archive from './Archive';
import Modal from './Modal';
import Welcome from './Welcome';
/* 
 * Here we import Login.jsx
 */ 
import Login from './Login';

export {
  About,
  Contact,
  Form,
  Game,
  GamesListManager,
  Home,
  Archive,
  Modal,
  Welcome,
  Login // Export Login
};

What about the login route? We wanto to show the login view at localhost:8080/auth/login so let's edit /client/src/routes.js:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
/* 
 * We can conveniently import Login with all the other components
 */ 
import { Home, Archive, Welcome, About, Contact, Login } from './components';
/* 
 * Here we imported syncHistoryWithStore
 */ 
import { syncHistoryWithStore } from 'react-router-redux';

const store = configureStore();
/* 
 * Sync navigation events with the store
 */ 
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});

const routes = (
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={Home}>
        <IndexRoute component={Welcome} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
      </Route>
      <Route path="/games" component={Archive}>
        <IndexRoute component={GamesContainer} />
        <Route path="add" component={AddGameContainer} />
      </Route>
      {
      /* 
       * The Archive component defines the layout that works for both Login and Signup
       */
    }
      <Route path="/auth" component={Archive}>
        <Route path="login" component={Login} />
       </Route>
    </Router>
  </Provider>
);

export default routes;

Notice the new Route with path /auth has component Archive, in fact the layout of the login page (and as we will see later, of the signup too) is very similar to the AddGame view so we can reuse it.

We haven't defined the action-creators yet, let's do it.

In /client/src/actions create a file auth.js and paste the following code:

// We always define constants
import {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
} from '../constants/auth';

// Intercepted by a redux-saga
function loginUser (redirection) {
  return {
    type: LOGIN_USER,
    redirection
  };
}

// In case of successful response from the server
function loginUserSuccess (token) { // It carries the token!
  return {
    type: LOGIN_USER_SUCCESS,
    token
  };
}

// In case of failure
function loginUserFailure () {
  return {
    type: LOGIN_USER_FAILURE
  };
}

export {
  loginUser,
  loginUserSuccess,
  loginUserFailure
};
  • The loginUser action-creator dispatch a LOGIN_USER action which is intercepted by a saga we are writing later.
  • The saga sends user credentials to the server and wait for a token, then call loginUserSuccesful or loginUserFailure according to the response.

You should be already familiar with the pattern of writing 3 action-creators.

Let's now define the constants, create a file auth.js in /src/client/constants:

// New constants for the login
const LOGIN_USER = 'LOGIN_USER';
const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
const LOGIN_USER_FAILURE = 'LOGIN_USER_FAILURE';

export {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
};

The code is pretty straightforward, we export the constants to be used throughout the project.

Then, let's create the reducer in a new file in /client/src/reducers/auth.js:

import Immutable from 'immutable';
// We neeed jwt-decode to take the user name from the token and store it in the state
import jwtDecode from 'jwt-decode';
import {
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
} from '../constants/auth';

// The initial state has no token hence no name and isAuthenticated is false
const initialState = Immutable.Map({
  isAuthenticated: false,
  token: null,
  name: null
});

export default (state = initialState, action) => {
  switch (action.type) {
    // Once the server sent a token, the saga dispatches loginUserSuccess 
    case LOGIN_USER_SUCCESS: { 
      return state.merge({
        isAuthenticated: true,
        token: action.token,
        name: jwtDecode(action.token).sub
      });
    }
    // In case of failure the state goes back to the initial one
    case LOGIN_USER_FAILURE: return state.merge(initialState);
    default: return state;
  }
}
  • isAuthenticated is a very important field, later we will use to query the state and see whether the user is authenticated or not.
  • Moreover, we decoded the token to get the user name and include it in the welcome message.

Have you noticed we imported jwt-decode? That's a simple library to help us decode the token in the frontend side of the app. Let's install it:

yarn add jwt-decode

Let's update /client/src/reducers/index.js:

import { combineReducers } from 'redux-immutable';
import { reducer as form } from 'redux-form/immutable';
import games from './games';
import filestack from './filestack';
/* 
 * Import the auth reducer
 */ 
import auth from './auth';
import routing from './routing';

export default combineReducers({
  games,
  form,
  filestack,
  auth, // Combine it with the other reducers
  routing
});

Finally, we need a new saga to communicate with the server. So create auth.js in /client/src/sagas and paste the following code:

import { takeLatest } from 'redux-saga';
import { put, call, select } from 'redux-saga/effects';
// We import the constant to use it in the watcher
import { LOGIN_USER } from '../constants/auth';
import {
  loginUserSuccess,
  loginUserFailure
} from '../actions/auth';
// push action-creators to change the view
import { push } from 'react-router-redux';
// We want to show a notification to the user once logged in
import {actions as toastrActions} from 'react-redux-toastr';

// Selector to get the credential from the form
const getForm = (state, form) => {
  return state.getIn(['form', form]).toJS();
}

// Fetch sends the credentials to the server
const sendCredentials = (route, credentials) => {
  return fetch(`http://localhost:8080/auth/${route}`, {
    headers: new Headers({
      'Content-Type': 'application/json'
    }),
    method: 'POST',
    body: JSON.stringify(credentials)
  })
  .then(response => {
    if (response.status === 200) {
      return response.json(); // This contains the token!
    }
    throw response;
  });
};

function* loginUser (action) {
  // The redirection changes the view to the main page
  const { redirection } = action;
  try {
    const credentials = yield select(getForm, 'login');
    const result = yield call(sendCredentials, 'login', credentials.values);
    // Redux-toastr shows the users nice notifications
    yield put(toastrActions.add({
       type: 'success', // success is a green notification
       title: 'Retrogames Archive',
       message: result.message
    }));
    // We also save the token in the local storage
    localStorage.setItem('token', result.token); 
    // We send the token to the reducer
    yield put(loginUserSuccess(result.token));
    // Redirect to the main page!
    yield put(push(redirection));
  } catch (e) {
    // The status 401 has a personalized message to show in a notification
    let message = '';
    if(e.status === 401) {
      message = 'Invalid email/password';
    } else {
      message = 'Sorry, an error occured!';
    }
    // Set the state to initial state
    yield put(loginUserFailure());
    yield put(toastrActions.add({
       type: 'error', // Red notification
       title: 'Retrogames Archive',
       message: message
     }));
  }
}

// Saga watcher to intercept LOGIN_USER
export function* watchLoginUser () {
  yield takeLatest(LOGIN_USER, loginUser);
}

Although the loginUser seems more complicated than the others we wrote in the past, it is actually very easy, the comments in the code highlight the steps. So, you must have notice redux-toastr that, as the name says, implement toastrs notifications to be used with redux.

We haven't added them yet, so let install it:

yarn add redux-toastr

Redux-toastr requires to add its own reducer, edit /client/src/reducers/index.js:

import { combineReducers } from 'redux-immutable';
import { reducer as form } from 'redux-form/immutable';
import games from './games';
import filestack from './filestack';
import auth from './auth';
import routing from './routing';
/* 
 * Import redux-toastr reducer
 */ 
import { reducer as toastr} from 'react-redux-toastr'

export default combineReducers({
  games,
  form,
  filestack,
  auth,
  routing,
  toastr // Combine toastr
});

Then, it requires its own css, edit /client/dist/index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Retrogames Archive</title>
    <link rel="icon" href="https://cdn.filestackcontent.com/S0zeyXxRem6pL6tHq9pz">
<!-- redux-toastr css to style the notifications -->
    <link href="http://diegoddox.github.io/react-redux-toastr/4.4/react-redux-toastr.min.css" rel="stylesheet" type="text/css">
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

  </head>
  <body>
    <div id="content"></div>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script src="https://api.filestackapi.com/filestack.js"></script>
    <script src="./bundle.js"></script>
  </body>
</html>

The last step is to add ReduxToastr component at the root of the app, let's edit /client/src/routes.js:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
import { Home, Archive, Welcome, About, Contact, Login } from './components';
import { syncHistoryWithStore } from 'react-router-redux';
/* 
 * Import ReduxToastr
 */ 
import ReduxToastr from 'react-redux-toastr';

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});

const routes = (
  <Provider store={store}>
  {
  /* 
   * We add the div wrapper as Route and ReduxToastr are on the same level 
   */
   }
    <div className="wrapper">
    <Router history={history}>
      <Route path="/" component={Home}>
        <IndexRoute component={Welcome} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
       </Route>
       <Route path="/games" component={Archive}>
         <IndexRoute component={GamesContainer} />
         <Route path="add" component={AddGameContainer} />
       </Route>
       <Route path="/auth" component={Archive}>
         <Route path="login" component={Login} />
       </Route>
    </Router>
    {
      /* 
       * we can customize it's behavior and look through props 
      /*
    }
    <ReduxToastr
        timeOut={2000}
        newestOnTop={false}
        preventDuplicates={true}
        position="top-right"
        transitionIn="fadeIn"
        transitionOut="fadeOut"
      />
   </div>
  </Provider>
);

export default routes;

For more information regarding Redux-toastr options, check the documentation.

The login logic is done, let's do the signup that you will see it's very similar.

User Signup

Let's create Signup.jsx in /client/src/components and paste the following code:

import React, { PureComponent } from 'react';
import { Link } from 'react-router';
import { Field, reduxForm } from 'redux-form/immutable';
import * as authActionCreators from '../actions/auth';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

class Signup extends PureComponent {
  // signupUser dispatches SIGNUP_USER to be intercepted by a redux-saga
  register () {
    this.props.authActions.signupUser();
  }

  render () {
    const { picture, uploadPicture } = this.props;
    return (
      <div className="row scrollable">
    <div className="col-md-offset-2 col-md-8">
         <div className="text-left">
            <Link to="/games" className="btn btn-info">Back</Link>
         </div>
      <div className="panel panel-default">
        <div className="panel-heading">
          <h2 className="panel-title text-center">
        Sign Up
          </h2>
        </div>
       <div className="panel-body">
        <form onSubmit={this.props.handleSubmit}>
                <div className="form-group text-left">
                  <label htmlFor="email">E-mail</label>
                  <Field
                    name="email"
                    type="text"
                    className="form-control"
                    component="input"
                    placeholder="Enter the e-mail"
                  />
                </div>
                <div className="form-group text-left">
                  <label htmlFor="name">Name</label>
                  <Field
                    name="name"
                    type="text"
                    className="form-control"
                    component="input"
                    placeholder="Enter the name"
                  />
                </div>
                <div className="form-group text-left">
                  <label htmlFor="password">Password</label>
                  <Field
                    name="password"
                    component="textarea"
                    className="form-control"
                    placeholder="Enter the password"
                    rows="5"
                  />
                </div>
        <button 
          type="button" 
          className="btn btn-submit btn-block" 
          onClick={() => this.register()}
        >
        Register
        </button>
        </form>
       </div>
      </div>
    </div>
     </div>
    );
  }
}
// Bint the action-creators to be used as props
function mapDispatchToProps (dispatch) {
  return {
    authActions: bindActionCreators(authActionCreators, dispatch)
  };
}
// redux-form HOC to wrap the component
export default reduxForm({ form: 'signup' })(connect(null, mapDispatchToProps)(Signup));

It's very similar to Login so let's move on and edit /client/src/components/index.js to make the component available:

import About from './About';
import Contact from './Contact';
import Form from './Form';
import Game from './Game';
import GamesListManager from './GamesListManager';
import Home from './Home';
import Archive from './Archive';
import Modal from './Modal';
import Welcome from './Welcome';
import Login from './Login';
/* 
 * Import Signup
 */ 
import Signup from './Signup';

export {
  About,
  Contact,
  Form,
  Game,
  GamesListManager,
  Home,
  Archive,
  Modal,
  Welcome,
  Login,
  Signup // Export Signup
};

And then we create its own route at /auth/signup. Let's edit /client/src/routes.js and paste the following:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
/* 
 * We also import Signup
 */ 
import { Home, Archive, Welcome, About, Contact, Login, Signup } from './components';
import { syncHistoryWithStore } from 'react-router-redux';
import ReduxToastr from 'react-redux-toastr';

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});

const routes = (
  <Provider store={store}>
    <div className="wrapper">
    <Router history={history}>
      <Route path="/" component={Home}>
        <IndexRoute component={Welcome} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
       </Route>
       <Route path="/games" component={Archive}>
         <IndexRoute component={GamesContainer} />
         <Route path="add" component={AddGameContainer} />
       </Route>
       <Route path="/auth" component={Archive}>
         {
         /* 
          * Signup Route 
          */
           }
         <Route path="signup" component={Signup} />
         <Route path="login" component={Login} />
       </Route>
    </Router>
    <ReduxToastr
        timeOut={2000}
        newestOnTop={false}
        preventDuplicates={true}
        position="top-right"
        transitionIn="fadeIn"
        transitionOut="fadeOut"
      />
   </div>
  </Provider>
);

export default routes;

To define the action-creators we are going to edit /client/src/actions/auth.js:

import {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  /*
   * New constants to be imported
   */ 
  SIGNUP_USER,
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
} from '../constants/auth';

function loginUser (redirection) {
  return {
    type: LOGIN_USER,
    redirection
  };
}

function loginUserSuccess (token) {
  return {
    type: LOGIN_USER_SUCCESS,
    token
  };
}

function loginUserFailure () {
  return {
    type: LOGIN_USER_FAILURE
  };
}
/* 
 * signupUser dispatched from Signup component
 */ 
function signupUser () {
  return {
    type: SIGNUP_USER
  };
}
/* 
 * SignupUserSuccess send the token to be added to the state
 */ 
function signupUserSuccess (token) { // It carries the token!
  return {
    type: SIGNUP_USER_SUCCESS,
    token
  };
}
/* 
 * In case of server failure
 */
function signupUserFailure () {
  return {
    type: SIGNUP_USER_FAILURE
  };
}

export {
  loginUser,
  loginUserSuccess,
  loginUserFailure,
  signupUser,
  signupUserSuccess,
  signupUserFailure
};

As we did for login, let's add the constants in /client/src/constants/auth.js:

const LOGIN_USER = 'LOGIN_USER';
const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
const LOGIN_USER_FAILURE = 'LOGIN_USER_FAILURE';
/* 
 * New constants
 */
const SIGNUP_USER = 'SIGNUP_USER';
const SIGNUP_USER_SUCCESS = 'SIGNUP_USER_SUCCESS';
const SIGNUP_USER_FAILURE = 'SIGNUP_USER_FAILURE';

export {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  SIGNUP_USER,
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
};

And now we are going to edit the reducer in /src/clients/reducers/auth.js:

import Immutable from 'immutable';
import jwtDecode from 'jwt-decode';
import {
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  /* 
   * New constants
   */ 
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
} from '../constants/auth';

const initialState = Immutable.Map({
  isAuthenticated: false,
  token: null,
  name: null
});
// The actions dispatched by the signup logic do exactly the same of login ones
export default (state = initialState, action) => {
  switch (action.type) {
    case SIGNUP_USER_SUCCESS:
    case LOGIN_USER_SUCCESS: {
      return state.merge({
        isAuthenticated: true,
        token: action.token,
        name: jwtDecode(action.token).sub
      });
    }
    case SIGNUP_USER_FAILURE: // All the failures simply return the initial state
    case LOGIN_USER_FAILURE: return state.merge(initialState);
    default: return state;
  }
}

Someone may wonder why we should define different constants for login and signup as they practically do the same: This is actually a personal choice, in my case I want to make it easier to read and understand that the action belongs to the signup logic instead of the login one.

Finally, let's edit /client/src/sagas/auth:

import { takeLatest } from 'redux-saga';
import { put, call, select } from 'redux-saga/effects';
import { LOGIN_USER, SIGNUP_USER } from '../constants/auth';
import {
  loginUserSuccess,
  loginUserFailure,
  /* 
   * We import the signup action-creators
   */
  signupUserSuccess,
  signupUserFailure
} from '../actions/auth';
import { push } from 'react-router-redux';
import {actions as toastrActions} from 'react-redux-toastr';

const getForm = (state, form) => {
  return state.getIn(['form', form]).toJS();
}

const sendCredentials = (route, credentials) => {
  return fetch(`http://localhost:8080/auth/${route}`, {
    headers: new Headers({
      'Content-Type': 'application/json'
    }),
    method: 'POST',
    body: JSON.stringify(credentials)
  })
  .then(response => {
    if (response.status === 200) {
      return response.json();
    }
    throw response;
  });
};

function* loginUser (action) {
  const { redirection } = action;
  try {
    const credentials = yield select(getForm, 'login');
    const result = yield call(sendCredentials, 'login', credentials.values);
    yield put(toastrActions.add({
       type: 'success',
       title: 'Retrogames Archive',
       message: result.message
    }));
    localStorage.setItem('token', result.token);
    yield put(loginUserSuccess(result.token));
    yield put(push(redirection));
  } catch (e) {
    let message = '';
    if(e.status === 401) {
      message = 'Invalid email/password';
    } else {
      message = 'Sorry, an error occured!';
    }
    yield put(loginUserFailure());
    yield put(toastrActions.add({
       type: 'error',
       title: 'Retrogames Archive',
       message: message
     }));
  }
}
/* 
 * the new sagas to handle signup
 */ 
function* signupUser () {
  try {
    // We get the credentials from the form in the state
    const credentials = yield select(getForm, 'signup');
    const result = yield call(sendCredentials, 'signup', credentials.values);
    // Show a notification in the browser 
    yield put(toastrActions.add({
       type: 'success',
       title: 'Retrogames Archive',
       message: result.message
    }));
    // Set the token in the local storage
    localStorage.setItem('token', result.token);
    // Update the state with the token
    yield put(signupUserSuccess(result.token));
    // Redirect to /games
    yield put(push('/games'));
  } catch (e) {
    // As we did for loginUser, we show a personalized message according to the error status
    let message = '';
    if(e.status === 409) {
      message = 'Email is already taken';
    } else {
      message = 'Sorry, an error occured!';
    }
    // Set the auth portion of the state to the initial value
    yield put(signupUserFailure());
    yield put(toastrActions.add({
       type: 'error',
       title: 'Retrogames Archive',
       message: message
     }));
  }
}

export function* watchLoginUser () {
  yield takeLatest(LOGIN_USER, loginUser);
}

/* 
 * Signup watcher
 */
export function* watchSignupUser () {
  yield takeLatest(SIGNUP_USER, signupUser);
}

And we are done for the sign-up logic! We need to run the two sagas watchLoginUser and watchSignupUser. Edit /client/src/sagas/index.js and paste the following code:

import {
  watchGetGames,
  watchDeleteGame,
  watchPostGame
} from './games';
import { watchUploadPicture } from './filestack';
/* 
 * The new watchers in charge of the authentication
 */ 
import { watchLoginUser, watchSignupUser } from './auth';

export default function* rootSaga () {
  yield [
    watchGetGames(),
    watchDeleteGame(),
    watchPostGame(),
    watchUploadPicture(),
    watchLoginUser(),
    watchSignupUser()
  ];
}

Now you can create a user and login by manually type /auth/signup or /auth/login.However the app has no buttons to login/sign-up, plus users who actually are not authenticated can still create or delete games. It's now time to protect the app and this concludes our tutorial.

Authentication Wrapper

So we want to protect some routes in our app and possibly hide/show the buttons to delete or add a game. To do so we are going to rely on redux-auth-wrapper. So what is this authentication wrapper? The idea is that we can decouple Authentication and Authorization with high order components that wrap other components, this is kinda useful to protect routes too!

Let's go on and install it:

yarn add redux-auth-wrapper

if you open /client/src/routes.js you should know that on /games/add the router is going to show AddGameContainer, however this should not happen when the user is not authenticated. Otherwise he/shes is going to be redirected to the login page.

Let's see it in action, we can create a utils.js file in /client/src and paste the following code:

 // We import the wrapper component
 import { UserAuthWrapper } from 'redux-auth-wrapper';

// We export a simple function which receives some options and return the wrapper
export default (options) => UserAuthWrapper(options);

The options we provide are useful to define customized rules for the app.

Now in /client/routes.js paste the following code:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, browserHistory, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
import { Home, Archive, Welcome, About, Contact, Login, Signup } from './components';
import { UserAuthWrapper } from 'redux-auth-wrapper';
import { push } from 'react-router-redux';
import { syncHistoryWithStore } from 'react-router-redux';
import ReduxToastr from 'react-redux-toastr';
/* 
 * We imported the utility function
 */ 
import userAuthenticated from './utils';

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});
/* 
 * Here we set the rules for the wrapper
 */ 
const options = {
  authSelector: state => state.get('auth'),
  predicate: auth => auth.get('isAuthenticated'),
  redirectAction: ({ pathname, query }) => {
    if(query.redirect) {
    // If the user is not logged in go to /auth/login
      return push(`auth${pathname}?next=${query.redirect}`);
    } 
  },
  wrapperDisplayName: 'UserIsJWTAuthenticated'
};
const requireAuthentication = userAuthenticated(options);

const routes = (
  <Provider store={store}>
    <div className="wrapper">
      <Router history={history}>
        <Route path="/" component={Home}>
          <IndexRoute component={Welcome} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Route>
        <Route path="/games" component={Archive}>
          <IndexRoute component={GamesContainer} />
       {
         /* 
          * you can see that AddGameContainer is now literally wrapped 
          */
          }
          <Route path="add" component={requireAuthentication(AddGameContainer)} />
        </Route>
        <Route path="/auth" component={Archive}>
          <Route path="signup" component={Signup} />
          <Route path="login" component={Login} />
        </Route>
      </Router>
      <ReduxToastr
        timeOut={2000}
        newestOnTop={false}
        preventDuplicates={true}
        position="top-right"
        transitionIn="fadeIn"
        transitionOut="fadeOut"
      />
    </div>
  </Provider>
);

export default routes;

So what have we actually done?

  • authSelector receives a selector of part of the state, in our case the auth has it's contain isAuthenticated
  • predicate receives the result from authSelector and according to the boolean balue of isAuthenticated it will either go to games/add or to auth/login.
  • As you can redirectAction is a redux action-creator, we check the redirection and in case push to /auth/login?next=query.redirect. query.redirect is actually /games/add so when the user logged in the view automatically shows the form to add a new game.
  • wrapperDisplayName is a name describing the authorization.

It's great, in few lines of code we have now a wrapper we can reuse to protect all the other routes that require authentication. However, we can do more than this, redux-auth-wrapper can actually hide/show any component, which is what we want to achieve: Show/hide the delete game button, or show the add game button when the user is authenticated while replacing it with login and sign-up buttons when not authenticated. Let's how easy it is!

Let's start from the delete button, to hide/show it we simply wrap it and pass the right options! Edit /client/src/components/Game.jsx and paste the following code:

import React, { PureComponent } from 'react';
import { Link } from 'react-router';
/* 
 * We import our utility function
 */ 
import userAuthenticated from '../utils';

/* 
 * Here we set the rules to hide/show the delete button
 */ 
const options = {
  authSelector: state => state.get('auth'),
  predicate: auth => auth.get('isAuthenticated'),
  wrapperDisplayName: 'authDeleteGame',
  FailureComponent: null
};

/* 
 * We define the 'wrapper' version of the delete button
 */ 
const DeleteButton = userAuthenticated(options)(
  (props) => <button className="btn btn-danger" role="button" onClick={() => props.deleteGame(props.id)}>Delete</button>
);

export default class Game extends PureComponent {
  render () {
    const { _id, i, name, description, picture, toggleModal, deleteGame } = this.props;
    return (
      <div className="col-md-4">
        <div className="thumbnail">
          <div className="thumbnail-frame">
            <img src={picture} alt="..." className="img-responsive thumbnail-pic" />
          </div>
          <div className="caption">
            <h5>{name}</h5>
            <p className="description-thumbnail">{`${description.substring(0, 150)}...`}</p>
            <div className="btn-group" role="group" aria-label="...">
              <button className="btn btn-success" role="button" onClick={() => toggleModal(i)}>View</button>
           {
           /* 
            * the new DeleteButton 
            */
           }
              <DeleteButton deleteGame={deleteGame} id={_id} />
            </div>
          </div>
        </div>
      </div>
    );
  }
}
  • As we have done before to protect the rule, we check inside the app state and if isAuthenticated the button will show up in the page.
  • FailureComponent is just a component we can render in the page when the user is not authorized. In this case it's set to null because there is no need but it comes handy with the sign-up/login panel instead.

Finally, we want to show the add game button when the user is authenticated, plus a welcome message and logout button. But, when the user is not logged in, then show the buttons sign-in and login.

Let's have another look at the UI:

  1. User Authenticated
  2. User not Authenticated

Let's create another component to achieve this, in /client/src/components create AddGamePanel.jsx and paste the following code:

import React, { PureComponent } from 'react';
import { Link } from 'react-router';
/* 
 * Import the utility function
 */ 
import userAuthenticated from '../utils';

class AddGamePanel extends PureComponent {
  render () {
    /* 
     * userName comes from the state while logout 
     * is an action creator we are going to define later 
     */
    const { userName, logout } = this.props;
    return (
      <div className="add-game-panel">
        <h5>Welcome back {userName}, <span onClick={logout}>Logout</span></h5>
        <Link to="/games/add" className="btn btn-danger">add a new Game!</Link>
      </div>
    );
  }
}
/* 
 * Auth-wrapper options
 */ 
const options = {
  authSelector: state => state.get('auth'),
  predicate: auth => auth.get('isAuthenticated'),
  wrapperDisplayName: 'authAddGame',
  /* 
   * This time the failure component are the buttons 
   * to authenticate the user or register a new one 
   */
  FailureComponent: () => {
    return (
      <div className="btn-group" role="group" aria-label="...">
        <Link to="/auth/signup" className="btn btn-primary">Sign Up</Link>
        <Link to="/auth/login" className="btn btn-danger">Login</Link>
      </div>
    );
  }
};

// We export it 
export default userAuthenticated(options)(AddGamePanel);

The options are totally the same as the delete buttons ones but this time we render FailureComponent when the user is not authenticated.

Now, let's import it inside GamesListManager, edit /client/src/components/GamesListManager.jsx:

import React, { PureComponent } from 'react';
import { Link } from 'react-router';
import Game from './Game';
/* 
 * Import the new component
 */
import AddGamePanel from './AddGamePanel';

export default class GamesListManager extends PureComponent {

  render () {
    const {
      games,
      searchBar,
      setSearchBar,
      toggleModal,
      deleteGame,
      userName,
      /*
    * The new action-creator to be defined
    */ 
      logout
    } = this.props;

    return (
      <div className="container scrollable">
        <div className="row text-left">
        {
        /* 
         * we add the component 
         */
        }
          <AddGamePanel logout={logout} userName={userName}/>
        </div>
        <div className="row">
          <input
            type="search" placeholder="Search by Name" className="form-control search-bar" onKeyUp={setSearchBar} />
        </div>
        <div className="row">
        {
          games
            .filter(game => game.name.toLowerCase().includes(searchBar))
            .map((game, i) => {
              return (
                <Game  {...game}
                  key={game._id}
                  i={i}
                  toggleModal={toggleModal}
                  deleteGame={deleteGame}
                />
              );
            })
        }
        </div>
        <hr />
      </div>

    );
  }
}

Nothing exotic, we just render the component inside its parent GamesListManager.

The userName and logout props come from the GamesContainer so let's edit /client/src/containers/GamesContainer.jsx:

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Immutable from 'immutable';
import { Modal, GamesListManager } from '../components';
import * as gamesActionCreators from '../actions/games';
import * as authActionCreators from '../actions/auth';
/* 
 * Add toastr to show notifcations
 */ 
import { toastr } from 'react-redux-toastr';

class GamesContainer extends PureComponent {
  constructor (props) {
    super(props);
    this.toggleModal = this.toggleModal.bind(this);
    this.deleteGame = this.deleteGame.bind(this);
    this.setSearchBar = this.setSearchBar.bind(this);
    // Bind logout to this
    this.logout = this.logout.bind(this);
  }

  componentDidMount () {
    this.getGames();
  }

  toggleModal (index) {
    this.props.gamesActions.showSelectedGame(this.props.games[index]);
    $('#game-modal').modal();
  }

  getGames () {
    this.props.gamesActions.getGames();
  }

  deleteGame (id) {
    this.props.gamesActions.deleteGame(id);
  }

  setSearchBar (event) {
    this.props.gamesActions.setSearchBar(event.target.value.toLowerCase());
  }
  /* 
   * The function calls an action to remove the user 
   * from the state, show a notification and    delete the token from the local storage 
   */
  logout () {
    this.props.authActions.logoutUser();
    toastr.success('Retrogames archive', 'Your are now logged out');
    localStorage.removeItem('token');
  }

  render () {
    const { games, selectedGame, searchBar, userName, authActions } = this.props;
    return (
      <div>
        <Modal game={selectedGame} />
        <GamesListManager
          games={games}
          searchBar={searchBar}
          setSearchBar={this.setSearchBar}
          toggleModal={this.toggleModal}
          deleteGame={this.deleteGame}
       {
       /* 
        * the new props to be passed 
        */
       }
          userName={userName}
          logout={this.logout}
        />
      </div>
    );
  }
}

function mapStateToProps (state) {
  return {
    games: state.getIn(['games', 'list'], Immutable.List()).toJS(),
    searchBar: state.getIn(['games', 'searchBar'], ''),
    selectedGame: state.getIn(['games', 'selectedGame'], Immutable.List()).toJS(),
    /* 
     * The name comes from token after being decoded
     */ 
    userName: state.getIn(['auth', 'name'])
  }
}

function mapDispatchToProps (dispatch) {
  return {
    gamesActions: bindActionCreators(gamesActionCreators, dispatch),
    authActions: bindActionCreators(authActionCreators, dispatch)
  };
}
export default connect(mapStateToProps, mapDispatchToProps)(GamesContainer);

As the container contains the logic it's a connected component, this is where we can get the props for AddGamePanel.

To conclude, let's create the logout action-creators in /client/src/actions/auth.js:

import {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  /* 
   * Import a new constant
   */ 
  LOGOUT_USER,
  SIGNUP_USER,
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
} from '../constants/auth';

function loginUser (redirection) {
  return {
    type: LOGIN_USER,
    redirection
  };
}

function loginUserSuccess (token) {
  return {
    type: LOGIN_USER_SUCCESS,
    token
  };
}

function loginUserFailure () {
  return {
    type: LOGIN_USER_FAILURE
  };
}
/* 
 * The action-creator
 */ 
function logoutUser () {
  return {
    type: LOGOUT_USER
  };
}

function signupUser () {
  return {
    type: SIGNUP_USER
  };
}

function signupUserSuccess (token) {
  return {
    type: SIGNUP_USER_SUCCESS,
    token
  };
}

function signupUserFailure () {
  return {
    type: SIGNUP_USER_FAILURE
  };
}

export {
  loginUser,
  loginUserSuccess,
  loginUserFailure,
  logoutUser,
  signupUser,
  signupUserSuccess,
  signupUserFailure
};

And then add the constant in /client/src/constants/auth.js:

const LOGIN_USER = 'LOGIN_USER';
const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
const LOGIN_USER_FAILURE = 'LOGIN_USER_FAILURE';
/* 
 * New constant
 */ 
const LOGOUT_USER = 'LOGOUT_USER';
const SIGNUP_USER = 'SIGNUP_USER';
const SIGNUP_USER_SUCCESS = 'SIGNUP_USER_SUCCESS';
const SIGNUP_USER_FAILURE = 'SIGNUP_USER_FAILURE';

export {
  LOGIN_USER,
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  LOGOUT_USER,
  SIGNUP_USER,
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
};

Finally, the auth reducer is going to intercept the action LOGOUT_USER, edit /client/src/reducers/auth:

import Immutable from 'immutable';
import jwtDecode from 'jwt-decode';
import {
  LOGIN_USER_SUCCESS,
  LOGIN_USER_FAILURE,
  /* 
   * Import the logout constant
   */ 
  LOGOUT_USER,
  SIGNUP_USER_SUCCESS,
  SIGNUP_USER_FAILURE
} from '../constants/auth';

const initialState = Immutable.Map({
  isAuthenticated: false,
  token: null,
  name: null
});

export default (state = initialState, action) => {
  switch (action.type) {
    case SIGNUP_USER_SUCCESS:
    case LOGIN_USER_SUCCESS: {
      return state.merge({
        isAuthenticated: true,
        token: action.token,
        name: jwtDecode(action.token).sub
      });
    }
    case SIGNUP_USER_FAILURE:
    case LOGIN_USER_FAILURE: // initial state is naturally the logout state
    case LOGOUT_USER: return state.merge(initialState);
    default: return state;
  }
}

And that's all, we added authentication to our Archive app!

There is only one last thing we can do: Whenever a user refreshes the page we should first look for the token in the local storage, in case it's valid we can directly authenticate him/her. To make sure of this edit /client/src/routes.js:

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './store';
import { Router, Route, browserHistory, hashHistory, IndexRoute } from 'react-router';
import { AddGameContainer, GamesContainer } from './containers';
import { Home, Archive, Welcome, About, Contact, Login, Signup } from './components';
import { UserAuthWrapper } from 'redux-auth-wrapper';
import { push } from 'react-router-redux';
import { syncHistoryWithStore } from 'react-router-redux';
/* 
 * import the action-creator
 */ 
import { loginUserSuccess } from './actions/auth';
import ReduxToastr from 'react-redux-toastr';
import userAuthenticated from './utils';

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store, {
  selectLocationState (state) {
    return state.get('routing').toObject();
  }
});

const options = {
  authSelector: state => state.get('auth'),
  predicate: auth => auth.get('isAuthenticated'),
  redirectAction: ({ pathname, query }) => {
    if(query.redirect) {
      return push(`auth${pathname}?next=${query.redirect}`);
    }
  },
  wrapperDisplayName: 'UserIsJWTAuthenticated'
};
const requireAuthentication = userAuthenticated(options);

const routes = (
  <Provider store={store}>
    <div className="wrapper">
      <Router history={history}>
        <Route path="/" component={Home}>
          <IndexRoute component={Welcome} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Route>
        <Route path="/games" component={Archive}>
          <IndexRoute component={GamesContainer} />
          <Route path="add" component={requireAuthentication(AddGameContainer)} />
        </Route>
        <Route path="/auth" component={Archive}>
          <Route path="signup" component={Signup} />
          <Route path="login" component={Login} />
        </Route>
      </Router>
      <ReduxToastr
        timeOut={2000}
        newestOnTop={false}
        preventDuplicates={true}
        position="top-right"
        transitionIn="fadeIn"
        transitionOut="fadeOut"
      />
    </div>
  </Provider>
);

/* 
 * If the token exists let's log the user
 */ 
const token = localStorage.getItem('token');
if (token !== null) {
    store.dispatch(loginUserSuccess(token));
}

export default routes;

And this is the end of the tutorial, Congratulations!

Conclusions

In this part.3 of the tutorial to create a Retrogame Archive app we added a simple authentication logic to limit the actions users can do when not logged in: They can only view the games, to modify the database they must be authorized.

To do so we first created two useful roues on our Node.js server to handle signup and login. Both returns a token that it's exchanged whenever the user wants to add or delete a game. To retrieve the token from HTTP requests we wrote a middleware.

In the client-side we added redux-auth-wrapper which helps us to write high order components to wrap routes or components. By doing so we can show/hide buttons and protect views from unauthorized users.

Further Improvements

It was definitely a long tutorial, divided in 3 parts, I hope you enjoyed it as I did while writing this fun app. However there are tons of optimization that can be done, first of all a general refactoring of the components: To name one, there may not be need separate components for signup and login views.

Here I summarizied a few suggestions:

  1. Writing constants and action-creators can become tedious (reducers included) so I suggest you to read the redux documentation to find strategies to reduce boilerplate.
  2. The same can be said for Redux-saga, take a look at the advanced concepts and you should get some hints on how to refactor the sagas we wrote.
  3. Finally, the webpack configuration to serve the bundle from Node.js is not optimized for production, which is why I set the NODE_ENV=build, not production. Also, Webpack 2 is at the time I wrote this tutorial very stable so I would suggest you all to start using it, the documentation to migrate from v1 to v2 is very complete.