Authentication with React, Redux, JSON Web Tokens, Node, & Sequelize : Part 2 of 4

Katie J. Duane

If you missed Part 1, you can find it here. In Part 2, we’ll do a few things:

  1. Review and set up Redux
  2. Configure our components to have access to the Redux store
  3. Create sign-up and sign-in logic from the front end

In later installments, we’ll discuss checking the token, middleware to check auth state on the backend, protecting front end routes, setting up an Axios instance with required headers, persisting auth state even after page refresh, and handling sign out.

A disclaimer: this tutorial assumes some basic knowledge of Redux, as well as previous use, however simple the application or confused about it you were (I was very lost the first few times I used Redux, and managed to make it work only by reading dozens of error messages). It also assumes basic knowledge of React, React Router, HTTP requests (we use Axios), and HTML forms. If you’re new to a lot of this, I do link to additional material throughout.

Part 2a: Redux in Index.js

Along with the Router (not covered in this tutorial, for more help with how to use Router, look here), we need to wrap our application in the Provider, which grants it access to the Redux store.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { store } from "./store/store/store";
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));

Part 2b: File structure

You certainly need not arrange your files the way I do, this is what makes sense for me, but I’ll explain my organizational logic nonetheless.

Components” are where I keep functional or ‘dummy’ components; they’re all stateless and very reusable. “Containers” are where I keep stateful, class-based components. Some (like a form) are re-usable, others, like the sign in page, are not. “Store” is where I keep anything Redux related. I separate, simply for clarity, actions and reducers into their own folders, and for this project, had a separate store file, which we will cover later. In this post, we’ll look at the Sign In & Sign Up containers, as well as Redux logic for handling these events. Let’s start with Sign Up!

Part 2c: SignUp component in React

Since there are many, many ways to create forms in React, I won’t go over that here, nor will I go over how to make and manage forms, or make POST requests using Axios; if you’re totally new to this, check out this link for React forms, and this link for Axios Requests. The focus here will be on connecting Redux to the component where your Sign Up form lives.

//SignUp.jsimport { connect } from "react-redux";
import { Redirect } from "react-router-dom";

First, we need “connect” from React-Redux; this is what enables React and Redux talk to each other. We also need the “Redirect” component from React-Router-DOM, which we will use to handle redirecting the user after they’re successfully registered.

submitHandler = event => {
event.preventDefault();
const firstname = this.state.controls.firstname.value;
const lastname = this.state.controls.lastname.value;
const email = this.state.controls.email.value;
const pass = this.state.controls.password.value;
this.props.onSignUp(firstname, lastname, email, pass);
};

The most important line in this submit handler is the last one: this.props.onSignUp(). For a quick Redux refresher → Redux handles application state. Everything in the Redux ‘store’ is state. When it is passed into a component, it is ‘props’. So we know that this.props.onSignUp() is a function that is coming from Redux. But how? How does it get here? This was what I struggled with the most when learning Redux, so let’s look!

const mapDispatchToProps = dispatch => {
return {
onSignUp: (firstname, lastname, email, password) =>
dispatch(actions.signUp(firstname, lastname, email, password))
};
};

mapDispatchToProps is a function that comes from Redux; it maps (sends) dispatched functions to, well, props. So you can use them in connected components! In this case, we want access to a function called onSignUp, which, within Redux, handles all the sign up logic, from sending the DB requests to ensuring the user has successfully registered on the front end. Additionally, we’ll need mapStateToProps, too. This function behaves similarly; it maps pieces of Redux state to props in connected components. In this component, we only want a slice of state called “registered”. We’ll see how that’s used later, but for now:

const mapStateToProps = state => {
return {
registered: state.auth.registered
};
};

Last, we need to connect this component to Redux so we have access to items in the store to be used at props →

export default connect(
mapStateToProps,
mapDispatchToProps
)(SignUp);

When using connect(), mapStateToProps must always be first, and mapDispatch second. If you’re only using one, send ‘null’ in the other’s place. Finally, we need to send the component we want to be connected, in this case, I am connecting my “Sign Up” component.

Part 2d: Signup logic in Redux

We will come back to React when we need to redirect our user after signing up, but for now, let’s examine signup logic in Redux. Now, this is a lot of code to look at, so let’s break it down, make some comparisons, and see how it all works together.

actionTypes.js

//signup
export const SIGNUP_START = "SIGNUP_START";
export const SIGNUP_SUCCESS = "SIGNUP_SUCCESS";
export const SIGNUP_FAIL = "SIGNUP_FAIL";

Action types are necessary. You need not assign them to variables like this, but I find that it prevents wasted time fixing typos. They’re essential because the type dictates the action, which dictates which function in the reducer is fired off. Which leads us to the actions themselves.

authActions.js

// =================== SIGNUP ACTIONS ==================== //export const signUpStart = () => {
return {
type: actionTypes.SIGNUP_START
};
};
export const signUpSuccess = authData => {
console.log(authData);
return {
type: actionTypes.SIGNUP_SUCCESS,
authData: authData
};
};
export const signUpFail = error => {
return {
type: actionTypes.SIGNUP_FAIL,
error: error
};
};

Note that ALL ACTIONS contain an action type. Everything else is extraneous, an action will be fired off without data (commonly called a payload), but nothing happens if there is no action type. Actions always return an object, which always contains the action type, and sometimes other data (from a database, or an error message for example). Below, we’ll look at the parent function that fires off (or dispatches) the actions above, I’ve made them bold for clarity.

export const signUp = (firstname, lastname, email, password) => {
return dispatch => {
dispatch(signUpStart());
const authData = {
firstname: firstname,
lastname: lastname,
email: email,
password: password
};
axios({
method: "POST",
url: `${window.apiHost}/users/signup`,
data: authData
})
.then(response => {
dispatch(signUpSuccess(response.data));
})
.catch(err => {
dispatch(
signUpFail("That email appears to already be in our database!")
);
});
};
};

So, looking this over, we can see this function dispatches various actions, and makes a request to our database with data from our signup form. But where are these actions going? And when they get dispatched, what happens, and where? How? Where does our authData or error get “processed” and how does it find its way back to the user? These were the questions I was asking myself when first learning Redux, and the only way I figured it all out was simply by doing it; and often, by doing it wrong, reading error messages, and making the required repairs to get things flowing to the right places, with the right data, and the right actions. My hope is to save you a few hours time and make a couple of essential things clear; though undoubtedly, you’ll have to go forward and make your own project, muck things up, and then do it correctly the second or third or tenth time around :)

Anyway. The answers to my (and maybe your) questions rest within the reducers.

authReducer.js

import * as actionTypes from "../actions/actionTypes";
import { updateObject } from "../utility";
const initialState = {
token: null,
firstname: null,
userId: null,
error: null,
loading: false,
authorized: false,
registered: null,
msg: ""
};
const signUpStart = (state, action) => {
return updateObject(state, { error: null, loading: true });
};
const signUpSuccess = (state, action) => {
return updateObject(state, {
msg: "signup success",
registered: true
});
};
const signUpFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
};

We’re going to have to examine some code side-by-side in a moment, but for now, let’s ascertain a few things:

Initial State is the state of the Redux store before any actions have been dispatched. The Redux store is also known as the “root reducer”, and it is where, if you have multiple reducers processing different actions, they will be combined. This is where things happen, and then are sent back out to the components that are connected so their state may be updated. It’s perhaps an oversimplified example, but for starters, imagine it as a loop:

  1. Our “SignUp’”component, initially, is just a form with a few fields.
  2. Upon signing up, an action is “dispatched”. This action carries action types, and data (from the form fields).
  3. It is sent to the DB, and then a response comes back (success or failure), which updates the “initial state”.
  4. The connected components receive this updated state, and then, if necessary, change somehow, such as displaying an error message, or in our case, redirect the user elsewhere (to sign in!).

Let’s look at some side-by-side code, focusing on only a few slices of state:

//action type
export const SIGNUP_SUCCESS = "SIGNUP_SUCCESS";
//action
export const signUpSuccess = authData => {
return {
type: actionTypes.SIGNUP_SUCCESS,
authData: authData
};
};
//reducer
const initialState = {
// i've taken out most of the redux state!
loading: false,
registered: false,
msg: ""
};
const signUpSuccess = (state, action) => {
return updateObject(state, {
msg: "signup success",
loading: false,
registered: true
});
};

Let’s walk through the above code step-by-step and follow the connecting threads! First, we declare the action type. This is dispatched only when signUp is successful; if there’s a failure, another action gets dispatched (signUpFail). If we look at the action itself, we see it returns an object with the required action type (SIGNUP_SUCCESS), and carries a payload, called ‘authData’, which is the response from the backend indicating whether registration was successful. Last, let’s examine the reducer. I’ve copied and pasted only the relevant slices of state here: loading, registered, and msg. the reducer is the function that updates state within the Redux store. In this case, because signUp was successful, we need to make sure “loading” is “false”, (during signUpStart, it is true), we change the “msg” to “signup success”, and most importantly, we adjust “registered” to “true”. This is the boolean value we’ll use to redirect successfully registered users later on!

updateObject.js

export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};

Additionally, we use a helper function called “updateObject” which shortens the amount of code we must write every time we update Redux state (i.e. The Store). You don’t need to use a helper function, but if you don’t, each reducer must update state immutably. Notice in signUpSuccess, we pass in “state” as the “oldObject”, and then also the specific properties within state that we want to change or update.

Part 2e: Sign In logic

Here, we have essential logic from our SignIn component. Again, I’m not including the form, simply the pieces needed to connect our component to Redux. Yours may look a bit different. Anywhere you see //… that means I’m leaving out code that is in fact essential, but not part of this tutorial. If you want to see the entire SignIn component I use, click here. SignUp can be found here.

SignIn.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { Link, Redirect } from "react-router-dom";
//...submitHandler = event => {
event.preventDefault();
const email = this.state.controls.email.value;
const pass = this.state.controls.password.value;
this.props.onSignIn(email, pass);
};
//...
render(){ //form and everything else goes here! }
const mapStateToProps = state => {
return {
loading: state.auth.loading,
authorized: state.auth.authorized,
error: state.auth.error
};
};
const mapDispatchToProps = dispatch => {
return {
onSignIn: (email, password) => dispatch(actions.signIn(email, password))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignIn);

Notice here that we want access to different state than we did in SignUp. Specifically, we’re looking to see what “authorized” is. If it’s false, sign in didn’t work, if it’s true, we can redirect them to a home page (later!). If it’s false, you’ll likely also render a message somewhere on the page so the user knows login didn’t work. In this case we use the “error” slice of state, to tell the user their email or password was not correct. The entire component is linked above if you want to see how I handle rendering this message.

actionTypes.js

export const SIGNIN_START = "SIGNIN_START";
export const SIGNIN_SUCCESS = "SIGNIN_SUCCESS";
export const SIGNIN_FAIL = "SIGNIN_FAIL";

authActions.js

// ================= SIGNIN ACTIONS =================== //export const signInStart = () => {
return {
type: actionTypes.SIGNIN_START
};
};
export const signInSuccess = (token, userId, firstname) => {
return {
type: actionTypes.SIGNIN_SUCCESS,
token: token,
userId: userId,
firstname: firstname
};
};
export const signInFail = error => {
return {
type: actionTypes.SIGNIN_FAIL,
error: error
};
};
export const signIn = (email, password) => {
return dispatch => {
dispatch(signInStart());
const authData = {
email: email,
password: password
};
axios({
method: "POST",
url: `${window.apiHost}/users/signin`,
data: authData
})
.then(response => {
console.log(response);
localStorage.setItem("token", response.data.token);
localStorage.setItem("userId", response.data.user.id);
localStorage.setItem("firstName", response.data.user.firstname);
dispatch(
signInSuccess(
response.data.token,
response.data.user.id,
response.data.user.firstname
)
);
})
.catch(err => {
console.log(err);
dispatch(signInFail("Email or password is invalid"));
});
};
};

Remember, on our backend, we created a JSON web token upon successful sign in. We need to store this token in local storage to retrieve when we send future requests to the backend. This is something we’ll cover in the next section, but for now, be sure to set them to local storage. We’ll talk about removing them, later as well, for sign out.

authReducer.js

const signInStart = (state, action) => {
return updateObject(state, { error: null, loading: true });
};
const signInSuccess = (state, action) => {
return updateObject(state, {
token: action.token,
userId: action.userId,
firstname: action.firstname,
error: null,
loading: false,
authorized: true
});
};
const signInFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
};

As you can see above, in signInSuccess, we’re updating a lot of Redux state. Thanks to the response from the backend, we now know our user’s name, userId, and we have access to their token, which we will need for future requests. We also change “authorized” to true, which we will use for redirecting! Before we talk about redirects though, we need to look at our Reducer function, which goes at the bottom of your reducer file, before you export. Essentially, this is an if-else block that evaluates the actionType, which results in the appropriate reducer function being fired off. Note that each function is passed state (initialState), and the action itself. The action determines what slices are updated, and how.

const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SIGNUP_START:
return signUpStart(state, action);
case actionTypes.SIGNUP_SUCCESS:
return signUpSuccess(state, action);
case actionTypes.SIGNUP_FAIL:
return signUpFail(state, action);
case actionTypes.SIGNIN_START:
return signInStart(state, action);
case actionTypes.SIGNIN_SUCCESS:
return signInSuccess(state, action);
case actionTypes.SIGNIN_FAIL:
return signInFail(state, action);
default:
return state;
}
};
export default reducer;

Last, if you’re new to Redux haven’t created the store already! We include something called Redux Thunk, which is middleware that allows Redux to handle async functions (like requests to a backend). applyMiddleware allows us to use middleware, and createStore does what it says: it creates our Redux store. Again, this tutorial assumes some basic knowledge of Redux. If this is all over your head, I suggest taking a look at this.

// store.js (though some people do this in App or Index)import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import authReducer from "../reducers/auth";
const rootReducer = combineReducers({
auth: authReducer,
// if you have any other reducers, they need to go here
});
const middleware = applyMiddleware(thunk);const theStore = middleware(createStore);export const store = theStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
// above line enables Redux Dev Tools!
);

Part 2f: Redirects

The last thing we’ll look at today are redirects. We’re going to use the Redirect component in both our SignUp and SignIn components. If you haven’t already, make sure you import it:

import { Redirect } from "react-router-dom";

Next, we only need to write just a couple of lines of code to get this working. In both SignUp and SignIn, we want to redirect the user if their auth action was successful. We want to redirect newly registered users to SignIn, and we want to redirect signed in users to Home. This will happen within the render() method for each:

SignUp.js

render() {
if (this.props.registered) {
return <Redirect to="/signin" />;
} else {
// whatever else would be rendered if they're not registered,
// the form, the loader, the error msg...

SignIn.js

render() {
if (this.props.authorized) {
return <Redirect to="/" />;
} else {
// whatever else would be rendered if they're not authorized,
// the form, the loader, the error msg...

For your application, the links might be ‘/login’ or ‘/home’; whatever they are, the same thing happens as long as the props are true. Earlier, we changed “registered” to true after successful sign up, and “authorized” to true after successful sign in. This code simply evaluates whether or not these props are true. Upon initial page load, they’ll be false, so the form will load. If there’s an error, they’ll still be false, so the page will re-render with an error message. And if sign up or sign in is successful, these slices of state are updated to true, sent down as props, and evaluated here, to true, initiating a Redirect! Again, if you want to look at the code for these components in its entirety, you can find the files here.

Below, I’ve linked to my GitHub gists which contain only the code we covered today. This is in no way complete and won’t function correctly until we learn how to check our tokens, and send them within the headers to our backend to make requests. We’ll cover token checks, headers, and backend middleware that checks auth state in the next article, as well as protecting routes on the front end! Until then, happy experimentin’!

❤ ❤ ❤

(The gists are slightly out of order because GitHub sorts them alphabetically!)

Katie J. Duane

Written by

i ❤ to write about art, tech, learning, and exploration

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade