This article was peer reviewed by Peleke Sengstacke. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
Redux is all the rage in the React community and beyond right now, and with good reason. It’s a library created by Dan Abramov that brings sanity to handling unidirectional data flow and allows developers to use powerful development features like time travel and record/replay.
Sounds great right? Here’s the rub: it comes at the price of needing to write more code. However, if you’ve had any experience maintaining large applications, you might know that data handling can become unwieldy and hard to manage. With Redux, we can have a clear view of the state of our application at all times and know exactly what our data is doing.
In this tutorial we’re going to take a look at how to get a start at creating a real-life React + Redux application that authenticates users and calls a remote API for data. Our app will retrieve a list of Star Wars Jedi from a Node backend so we can display their names and photos. For authentication we’re going to use Auth0 so that we can get up and running quickly and also easily get features like social login and multifactor authentication.
We won’t dive into the elementary concepts of Redux, so if you’re new to the library, check out some of these great getting started resources:
The source code for the app we’ll be building can be downloaded here.
Redux Authentication: Getting Started
Our React project for this tutorial will be written in ES2015 so we’ll use Babel to transpile down to ES5, along with webpack to handle module bundling. Instead of setting things up from scratch, why don’t we start with Dan’s real-world starter example in the Redux repo. Grab a copy of that and install the dependencies.
npm install
Sign up for Auth0
The best way to do authentication for single page apps (like the one we’re building) is to use JSON Web Tokens (JWT). JWTs provide a way to do stateless authentication against a RESTful API, and this offers many benefits over session and cookie-based auth. The downside is that rolling our own solution for JWT authentication can be tricky and error prone, but fortunately we can use Auth0 and not worry about any server or security implementation details.
If you haven’t already done so, head over and sign up for a free Auth0 account. With the free plan we get 7,000 regular active users and can use two social identity providers.
After registering, follow the prompts to initialize your account. Keep in mind that you can have multiple applications under the same account, so select a domain name that is suitable to your situation—perhaps the name of your organization. As a first step, we need to set our localhost
URL as an allowed origin. This can be done in the “Allowed Origins (CORS)” textarea.
Set up the Web Server
Why don’t we get the Jedi web server out of the way first. This just needs to be a simple RESTful API that returns our Jedis in the form of JSON data, and quick way to do this is with NodeJS using the Express framework. You can use any server-side language or framework you like, as long as JSON data is returned.
Note: The Star Wars purists will note that we’re using “Jedis” as the plural form of Jedi throughout the application, but this isn’t the correct pluralization. Rather, we should simply be using “Jedi”. Maybe so, but I’m ok with it because it makes things easier in our app :)
First, initialize an app and install the dependencies:
mkdir server && cd server
touch server.js
npm init
npm install express express-jwt cors
We can provide all the code that our server will need in a single JavaScript file.
// server.js
const express = require('express');
const app = express();
const jwt = require('express-jwt');
const cors = require('cors');
app.use(cors());
app.use(express.static('public'));
// Authentication middleware provided by express-jwt.
// This middleware will check incoming requests for a valid
// JWT on any routes that it is applied to.
const authCheck = jwt({
secret: 'AUTH0_SECRET',
// If your Auth0 client was created before Dec 6, 2016,
// uncomment the line below and remove the line above
// secret: new Buffer('AUTH0_SECRET', 'base64'),
audience: 'AUTH0_CLIENT_ID'
});
var jedis = [
{
id: 1,
name: 'Luke Skywalker',
image: 'http://localhost:7000/images/luke-skywalker.jpg'
},
{
id: 2,
name: 'Anakin Skywalker',
image: 'http://localhost:7000/images/anakin-skywalker.png'
},
{
id: 3,
name: 'Yoda',
image: 'http://localhost:7000/images/yoda.png'
},
{
id: 4,
name: 'Obi-Wan Kenobi',
image: 'http://localhost:7000/images/obi-wan-kenobi.jpg'
},
{
id: 5,
name: 'Mace Windu',
image: 'http://localhost:7000/images/mace-windu.jpg'
}
];
app.get('/api/jedis', (req, res) => {
const allJedis = jedis.map(jedi => {
return { id: jedi.id, name: jedi.name }
});
res.json(allJedis);
});
app.get('/api/jedis/:id', authCheck, (req, res) => {
res.json(jedis.filter(jedi => jedi.id === parseInt(req.params.id))[0]);
});
app.listen(7000);
console.log('Listening on http://localhost:7000');
We’ve got an array of Jedis and two endpoints to handle them. The first endpoint returns all of the Jedis, but only their id
and name
properties. The second endpoint at /jedis/:id
returns a single Jedi, but also includes the image URL too. This second endpoint is the one that we are going to protect with our authentication middleware and have it limited to only authenticated users.
But how do we actually protect this endpoint? We’re using express-jwt to create a middleware that looks for an incoming JSON Web Token and verifies it against a secret key that we provide. We can then apply this middleware to any of our endpoints—which we are doing with the second argument to the /jedis/:id
endpoint—and only those requests that include a valid token with them will be allowed through.
The middleware itself is setup by supplying our Auth0 secret key and client ID to authCheck
, and this is where you can provide the keys specific to your application. These keys can be found in the Auth0 management dashboard under applications.
The Jedi images are coming from a public
directory on the server. You can grab the same images from the repo or you can include links to images from other sources in the data if you like as well.
With the server in place, let’s verify that the API is working as expected. We can do this with a tool like Postman.
If we go to the /api/jedis
route, we’re able to get the full list of Jedis as expected. However, if we try to get one Jedi, we aren’t allowed to get the resource back because we aren’t sending a token to the server.
We’ll see how to send tokens with our requests once we implement our API calls in the application itself, but essentially all we need to do is include it in an Authorization
header using the Bearer
scheme.
Setting up Redux
Auth0Lock
Auth0 accounts come with an awesome pre-built widget called Lock which greatly simplifies the process of actually logging into an app. We can get the JavaScript required for this widget from Auth0’s CDN.
<!-- index.html -->
<!-- Auth0Lock script -->
<script src="//cdn.auth0.com/js/lock-9.min.js"></script>
<!-- Setting the right viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
Routing
We’re going to limit our application to a single route for the sake of brevity. There are some authentication concerns when it comes to routing, such as limiting certain routes to only those users who are authenticated, but that’s the subject for a future article. For now, let’s keep things simple and define a single route.
// routes.js
import React from 'react'
import { Route } from 'react-router'
import App from './containers/App'
export default (
<Route path="/" component={App}></Route>
)
This gives us a base route that uses a component called App
.
Setting up the Actions
Reducers are at the heart of Redux, and they give us a clean and predictable way to change the state of our application. When using Reducers, we need to make sure that no data gets mutated. This gives us the benefit of being able to inspect every previous state of the data in our app, and it’s an important concept in Redux.
While reducers are the crux, we still need actions to make things happen in the application. Let’s put in all of the actions we’ll need for our Jedis application.
// actions/index.js
import { CALL_API } from '../middleware/api'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGIN_ERROR = 'LOGIN_ERROR'
function loginSuccess(profile) {
return {
type: LOGIN_SUCCESS,
profile
}
}
function loginError(err) {
return {
type: LOGIN_ERROR,
err
}
}
export function login() {
const lock = new Auth0Lock('AUTH0_CLIENT_ID', 'AUTH0_DOMAIN')
return dispatch => {
lock.show((err, profile, token) => {
if(err) {
return dispatch(loginError(err))
}
localStorage.setItem('profile', JSON.stringify(profile))
localStorage.setItem('id_token', token)
return dispatch(loginSuccess(profile))
})
}
}
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
function logoutSuccess(profile) {
return {
type: LOGOUT_SUCCESS
}
}
export function logout() {
return dispatch => {
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
return dispatch(logoutSuccess());
}
}
export const JEDIS_REQUEST = 'JEDIS_REQUEST'
export const JEDIS_SUCCESS = 'JEDIS_SUCCESS'
export const JEDIS_FAILURE = 'JEDIS_FAILURE'
function fetchJedis() {
return {
[CALL_API]: {
types: [ JEDIS_REQUEST, JEDIS_SUCCESS, JEDIS_FAILURE ],
endpoint: 'jedis',
authenticatedRequest: false
}
}
}
export function loadJedis() {
return dispatch => {
return dispatch(fetchJedis())
}
}
export const JEDI_REQUEST = 'JEDI_REQUEST'
export const JEDI_SUCCESS = 'JEDI_SUCCESS'
export const JEDI_FAILURE = 'JEDI_FAILURE'
function fetchJedi(id) {
return {
[CALL_API]: {
types: [ JEDI_REQUEST, JEDI_SUCCESS, JEDI_FAILURE ],
endpoint: `jedis/${id}`,
authenticatedRequest: true
}
}
}
export function loadJedi(id) {
return dispatch => {
return dispatch(fetchJedi(id))
}
}
The const
values that we’re exporting are the “action types” that we want to be able to listen for in our components, which we’ll see later. When we’re dealing with making requests to an API, we generally want to have three action types: one to signal that we’ve made a request, one that handles the success case, and finally, one that handles the error case. This is exactly what we’re doing with our API calls, and we’re setting up some functions that rely on a great feature that Redux allows for: middleware.
With middleware in Redux, we have a lot of possibilities, including such things as logging. We can also use middleware to handle our actual API calls, and we’re relying on it here in our actions to make the requests to our API. We’ll see the full API middleware code shortly.
Redux actions rely on us using dispatch
to actually kick them into gear, and this is what we do in all of our exported action functions. The idea is that we can dispatch an action based on some event in our React components, and the action type associated with that action will trigger the reducer to respond accordingly. That response is what tells our application that part of it must be updated.
It’s worth noting that the actions we’ve set up for Lock only have two types: one for success and the other for failure. Why not an action type for when Lock opens and when the request is made? It comes down to whether or not we actually need to know about these actions. What do we gain from knowing that the Lock widget has been opened? Perhaps there is a use case for it somewhere, but for our app we really just need to know if the user has been successfully authenticated or not.
The login
function is where the actual Auth0 authentication magic happens. The Lock widget gives us a show
method which is what actually makes the widget appear. Once we enter our credentials and hit submit, the callback handles what comes next. If we hit an error for any reason, we dispatch
the error type with the message.
If everything works out, however, we save the JWT and user profile object that come back from the response in local storage so they can be recalled and used later in the application. We also dispatch
with the success type if this happens and pass the profile through so it can be used in the reducer if necessary.
Now that we’re exporting some action functions, we can use them in our components. Before we can do that, however, we need to create the API middleware that we’re making use of with CALL_API
.
Create the API Middleware
As previously mentioned, Redux middleware opens up a lot of possibilities in our applications. They are also the perfect mechanism to handle API calls. We can create a single generic API middleware and then use it in any of our actions to make calls to our backend. We’ll use Fetch to make calls to the API and provide options for sending both authenticated and unauthenticated requests.
// middleware/api.js
export const API_ROOT = 'http://localhost:7000/api/'
function callApi(endpoint, authenticatedRequest) {
let token = localStorage.getItem('id_token') || null
let config = {}
if(authenticatedRequest) {
if(token) {
config = {
headers: { 'Authorization': `Bearer ${token}` }
}
} else {
throw new Error("No token saved!")
}
}
return fetch(API_ROOT + endpoint, config)
.then(response =>
response.json()
.then(resource => ({ resource, response }))
).then(({ resource, response }) => {
if (!response.ok) {
return Promise.reject(resource)
}
return resource
})
}
export const CALL_API = Symbol('Call API')
export default store => next => action => {
const callAPI = action[CALL_API]
if (typeof callAPI === 'undefined') {
return next(action)
}
let { endpoint, types, authenticatedRequest } = callAPI
if (typeof endpoint !== 'string') {
throw new Error('Specify a string endpoint URL.')
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error('Expected an array of three action types.')
}
if (!types.every(type => typeof type === 'string')) {
throw new Error('Expected action types to be strings.')
}
function actionWith(data) {
const finalAction = Object.assign({}, action, data)
delete finalAction[CALL_API]
return finalAction
}
const [ requestType, successType, failureType ] = types
next(actionWith({ type: requestType }))
return callApi(endpoint, authenticatedRequest).then(
response => next(actionWith({
response,
authenticatedRequest,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || 'Error!'
}))
)
}
Starting at the top, we define our base API endpoint and then create a function for making the API calls. This function accepts arguments for both an endpoint that the request should go to, and a boolean for whether or not the request should be authenticated. If it should be a secure request, we set up an Authorization
header that contains the user’s JWT from local storage.
As we saw in our actions, when we use the CALL_API
symbol to trigger the middleware, we provide an array of all three of our action types to the types
key. These action types are being picked up in the actions so that they can be passed along to our reducer. As we might expect, the successType
is used in the success case in the then
method of the callApi
function, and the errorType
is used in the error case.
Implement the Reducers
We’re at the last stage before we can actually use Redux in our React components! We need to implement a Redux reducer to actually respond to our actions and return data in an immutable fashion. We need to check the expiry for the user’s JWT and we can do this easily with the jwt-decode library from Auth0, so let’s install that now.
npm install jwt-decode
With that in place, let’s create the reducer.
// reducers/index.js
import * as ActionTypes from '../actions'
import { routerReducer as routing } from 'react-router-redux'
import { combineReducers } from 'redux'
const jwtDecode = require('jwt-decode')
function checkTokenExpiry() {
let jwt = localStorage.getItem('id_token')
if(jwt) {
let jwtExp = jwtDecode(jwt).exp;
let expiryDate = new Date(0);
expiryDate.setUTCSeconds(jwtExp);
if(new Date() < expiryDate) {
return true;
}
}
return false;
}
function getProfile() {
return JSON.parse(localStorage.getItem('profile'));
}
function auth(state = {
isAuthenticated: checkTokenExpiry(),
profile: getProfile(),
error: ''
}, action) {
switch (action.type) {
case ActionTypes.LOGIN_SUCCESS:
return Object.assign({}, state, {
isAuthenticated: true,
profile: action.profile,
error: ''
})
case ActionTypes.LOGIN_ERROR:
return Object.assign({}, state, {
isAuthenticated: false,
profile: null,
error: action.error
})
case ActionTypes.LOGOUT_SUCCESS:
return Object.assign({}, state, {
isAuthenticated: false,
profile: null
})
default:
return state
}
}
function jedis(state = {
isFetching: false,
allJedis: [],
error: ''
}, action) {
switch (action.type) {
case ActionTypes.JEDIS_REQUEST:
return Object.assign({}, state, {
isFetching: true
})
case ActionTypes.JEDIS_SUCCESS:
return Object.assign({}, state, {
isFetching: false,
allJedis: action.response,
error: ''
})
case ActionTypes.JEDIS_FAILURE:
return Object.assign({}, state, {
isFetching: false,
allJedis: [],
error: action.error
})
default:
return state
}
}
function jedi(state = {
isFetching: false,
singleJedi: {},
error: ''
}, action) {
switch (action.type) {
case ActionTypes.JEDI_REQUEST:
return Object.assign({}, state, {
isFetching: true
})
case ActionTypes.JEDI_SUCCESS:
return Object.assign({}, state, {
isFetching: false,
singleJedi: action.response,
error: ''
})
case ActionTypes.JEDI_FAILURE:
return Object.assign({}, state, {
isFetching: false,
singleJedi: {},
error: action.error
})
default:
return state
}
}
const rootReducer = combineReducers({
routing,
auth,
jedis,
jedi
})
export default rootReducer
A reducer essentially defines an initial state and then returns a new state based on an action. The newly returned state must be a new object and not simply a mutated version of the original state. That’s why we’re using Object.assign
and passing in an empty object as the first argument; it gives us a guarantee that the new state will be unique.
The auth
reducer starts out by checking whether the user’s JWT is expired or not. This isAuthenticated
boolean will be used throughout the application to conditionally hide and show various elements that are dependent on the user’s authentication state.
When the user logs in successfully, their isAuthenticated
state is set to true and their profile is also set to the profile object that comes from Auth0. This profile object has properties like picture
, nickname
, and some others that are useful for showing a profile area.
The reducers for our Jedi requests have an isFetching
boolean, as well as the data coming from the actions.
Finally, we wrap all of these individual reducers up into a single rootReducer
by using combineReducers
. Now let’s move on to the actual application code!
Create the App Component
The first of our components will be a root container component called App
.
// containers/App.js
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { loadJedis, loadJedi, login, logout } from '../actions'
import JedisList from '../components/JedisList'
import Jedi from '../components/Jedi'
import Auth from '../components/Auth'
class App extends Component {
constructor(props) {
super(props)
this.handleGetJedisClick = this.handleGetJedisClick.bind(this)
this.handleGetJediClick = this.handleGetJediClick.bind(this)
this.handleLoginClick = this.handleLoginClick.bind(this)
this.handleLogoutClick = this.handleLogoutClick.bind(this)
}
handleGetJedisClick() {
this.props.loadJedis()
}
handleGetJediClick(id) {
this.props.loadJedi(id)
}
handleLoginClick() {
this.props.login()
}
handleLogoutClick() {
this.props.logout()
}
render() {
const { allJedis, singleJedi, error, isAuthenticated, profile } = this.props
return (
<div>
<div className="navbar navbar-default">
<div className="container-fluid">
<a className="navbar-brand">Redux Jedi</a>
<Auth
isAuthenticated={isAuthenticated}
profile={profile}
onLoginClick={this.handleLoginClick}
onLogoutClick={this.handleLogoutClick}
/>
</div>
</div>
<div className="container-fluid">
<JedisList
jedis={allJedis}
error={error}
onClick={this.handleGetJedisClick}
onGetJediClick={this.handleGetJediClick}
isAuthenticated={isAuthenticated}
/>
<Jedi jedi={singleJedi} />
</div>
</div>
)
}
}
function mapStateToProps(state) {
const { jedis, jedi, auth } = state
const { allJedis, error } = jedis
const { singleJedi } = jedi
const { isAuthenticated, profile } = auth
return {
allJedis,
singleJedi,
error,
isAuthenticated,
profile
}
}
export default connect(mapStateToProps, {
loadJedis,
loadJedi,
login,
logout
})(App)
Most of what’s happening here is pretty standard React: we’re bringing in a couple other components that we’ll create in the next section, and we’re passing props down to them. The props that we pass down include our authentication and Jedis data, as well as some handler functions which will be used to respond to click events.
One thing that is different when using Redux is that we need to supply a state map and object of our actions to the connect
function. This function essentially connects our React component to the Redux store that is supplied in the store
directory. When we supply the mapStateToProps
function, the component will update data whenever it changes as a result of our actions being dispatched.
Let’s now create the other components we’ll need.
Create the Auth Component
The Auth
component will be where we display the “Login” and “Logout” buttons, as well as the user’s profile photo and nickname.
// components/Auth.js
import React, { Component, PropTypes } from 'react'
export default class Auth extends Component {
constructor(props) {
super(props)
}
render() {
const { onLoginClick, onLogoutClick, isAuthenticated, profile } = this.props
return (
<div style={{ marginTop: '10px' }}>
{ !isAuthenticated ? (
<ul className="list-inline">
<li><button className="btn btn-primary" onClick={onLoginClick}>Login</button></li>
</ul>
) : (
<ul className="list-inline">
<li><img src={profile.picture} height="40px" /></li>
<li><span>Welcome, {profile.nickname}</span></li>
<li><button className="btn btn-primary" onClick={onLogoutClick}>Logout</button></li>
</ul>
)}
</div>
)
}
}
Now when users click the “Login” button, Auth0’s Lock widget will pop up.
After a successful login, the user’s profile image and nickname are displayed in the navbar, along with the “Logout” button.
Now that we can log into the app, let’s get the Jedi
and JedisList
components in place.
Create the Jedi and JedisList Components
We want a sidebar to list out all of our Jedis.
// components/JediList.js
import React, { Component, PropTypes } from 'react'
function getJediListItem(jedi, isAuthenticated, onClick) {
return(
<li key={jedi.id} className="list-group-item">
{ isAuthenticated ? (
<a onClick={() => onClick(jedi.id)}>
<h4 style={{ cursor: 'pointer' }}>{jedi.name}</h4>
</a>
) : (
<h4>{jedi.name}</h4>
)}
</li>
)
}
export default class JedisList extends Component {
constructor(props) {
super(props)
}
render() {
const { jedis, error, onClick, onGetJediClick, isAuthenticated } = this.props
const jedisList = jedis.map(jedi => getJediListItem(jedi, isAuthenticated, onGetJediClick))
return (
<div className="col-sm-3">
<button className="btn btn-primary" onClick={onClick} style={{ marginBottom: '10px' }}>Get Jedis</button>
{ jedis &&
<ul className="list-group">
{jedisList}
</ul>
}
{ error &&
<span className="text-danger">{error}</span>
}
</div>
)
}
}
When we click the “Get Jedis” button, our loadJedis
function gets called which dispatches an action to make a fetch request to the server.
The HTML that we are constructing for our list of Jedis includes a click handler that uses onGetJediClick
to fetch an individual Jedi. Since we’re protecting the endpoint that returns individual Jedis with authentication middleware, we don’t want to make them clickable if the user isn’t authenticated, so we’re checking the isAuthenticated
value to conditionally return an unclickable list.
Now we just need the Jedi
component.
// components/Jedi.js
import React, { Component, PropTypes } from 'react'
export default class Jedi extends Component {
render() {
const { jedi } = this.props
return (
<div className="col-sm-9">
{ jedi &&
<div>
<h2>{jedi.name}</h2>
<img src={jedi.image} />
</div>
}
</div>
)
}
}
This component simply displays the individual Jedi’s name and picture.
Wrapping Up
Using Redux definitely means we need to write more code to get things done, but this can be a fair price to pay to make our data flow predictable, which is key as our apps start to grow. As was mentioned throughout the article, using Redux also gives us the benefit of being able to tap into cool features such as time travel and record/replay.
Authentication can be tricky for any single page application, but Auth0 makes it easy to get up and running with it quickly. Using Auth0 also means we can instantly implement features like social login and multifactor authentication