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: 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.1.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.
Continue reading %Redux Authentication: Secure Your Application with Auth0%
by Ryan Chenkie via SitePoint
No comments:
Post a Comment