0% found this document useful (0 votes)
3 views

Building a secure single-page application (SPA) with React.js _ by Colin Kraczkowsky _ Level Up Coding

The document outlines the process of building a secure single-page application (SPA) using React.js for the frontend and Node.js, Express, and MongoDB for the backend. It details the setup of the project structure, the creation of components, the implementation of routing with React Router, and the development of a login feature using axios for authentication. The tutorial emphasizes security by restricting access to certain content based on user authentication.

Uploaded by

kagohi6923
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Building a secure single-page application (SPA) with React.js _ by Colin Kraczkowsky _ Level Up Coding

The document outlines the process of building a secure single-page application (SPA) using React.js for the frontend and Node.js, Express, and MongoDB for the backend. It details the setup of the project structure, the creation of components, the implementation of routing with React Router, and the development of a login feature using axios for authentication. The tutorial emphasizes security by restricting access to certain content based on user authentication.

Uploaded by

kagohi6923
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

Sign In Get started

Published in Level Up Coding

You have 2 free member-only stories left this month.


Sign up for Medium and get an extra one

Colin Kraczkowsky Follow

May 15, 2020 · 27 min read · Listen

Building a secure single-page application (SPA)


with React.js
Full stack application with Node.js, Express, MongoDB, and React.js

67 2

In this blog, I build a frontend application that interfaces with a backend


application via a RESTful API. After building the frontend, I add security so that
only authorized users can access certain content and make requests to the
backend.

The frontend application is built with a collection of JavaScript libraries:


React.js, React Router, and axios. The backend application is built with Node.js,
Express.js, and MongoDB and is the result of a project discussed in my previous
blogs where I detailed how to build a RESTful API using Node.js, Express.js, and
MongoDB and how to secure a RESTful API built with Node.js. To follow along,
you can find the source code on my GitHub.

Setting up a React.js application for our frontend


Adding a frontend means that we will now have two applications running in
different locations, i.e. on the same machine but on different ports or on two
different machines hundreds or thousands of miles apart. So let’s start with
restructuring the project that we worked on in my previous blog Securing a
RESTful API Built with Node.js.

First, we create a new directory called backend and migrate all of our backend
code into it.

$ mkdir backend

Let’s make sure that our Node.js application still works after the migration by
switching into the new directory and starting the application.

$ cd backend/
$ node ./index.js

Back in the root directory of our project, we create a new directory called
frontend where we will add all of our frontend code.

$ mkdir frontend

Recall from my previous blog Building a RESTful API with Node.js that the
frontend of our Node.js application was the very basic index.html file. Later in
this section, we will enhance this file but for now let’s move it from our backend
directory to our frontend directory.

$ mv backend/index.html frontend/

Let’s turn this into a React.js application! The foundations of a React.js


application are the index.html file and the index.js file. We already have an
index.html file, so we just need to create the index.js file.

$ cd frontend/
$ touch index.js

And we move the code describing the content that the browser to display from
our index.html file into our new index.js file.

import React from ‘react’;


import ReactDOM from ‘react-dom’;

ReactDOM.render(
<div>
<h1>Hello World!</h1>
<h2>Item List</h2>
<ul>
<li>Item 1</li>
</ul>
<h2>Add Item</h2>
<form>
<label for=”item”>Item: </label>
<input type=”text”></input>
<br></br>
<input type=”submit” value=”Submit”></input>
</form>
<a href=”/content”>Click here for content!</a>
</div>, document.getElementById(‘root’)
);

Going into the specifics of React.js is out of the scope of this blog, so I will just
summarize what this code is doing. We load the react and react-dom libraries
which give us access to JSX, a syntax extension to JavaScript, and the Virtual
Document Object Model (VDOM), a node tree of React components that matches
actions to state. ReactDOM.render() creates this VDOM in which we use JSX to
render a React element. We wrap the HTML from our old index.html file in a
div element because React can only render a single element at a time.

As you can see, this file loads two new libraries, react and react-dom , so we

need to install their respective packages which we do using a familiar tool: the
Node package manager (npm).

$ npm install react react-dom

Next, we refactor the index.html file.

<!doctype html>
<html>
<head>
</head>
<body>
<div id=”root”>
</body>
</html>

Recall from the index.js file that we supplied ReactDOM.render() with a second
argument. This argument is the container in which we want our React elements
rendered. We defined this container as document.getElementById(‘root’) which
grabs a reference to the DOM and asks it to return the element with an id of
‘root’. In the index.html file we supply this element. In fact, this is the only
element that we will supply in the index.html file. All other elements will be
rendered to the VDOM within this container element. This is very different from
early web development where a website was a series of HTML documents linked
together each of which were their own DOM object. As we build out our React.js
application, you will notice that we manipulate the presentation of the content
all within this single DOM object.

Adding content to our React.js application


Let’s make our React.js application more interesting by adding some content to
present. A React.js application is a composition of React components which are
similar to JavaScript functions. They accept an assortment of inputs called props

and return React elements describing what should appear on the screen.
Components are just JavaScript files, so let’s add one to our project.

$ touch PublicPage.js

We then define what this component should return.

import React from ‘react’;

export default class PublicPage extends React.Component{


render(){
return(
<div>
<h1>This is a public page, everyone has access to this page!
</h1>
</div>
);
};
};

Like we did in the index.js file, we load the react library to get it in scope so
that we have access to JSX syntax for use in our component. We then create an
instance of the Component class provided by React and name it PublicPage , it is

this name that we will use to refer to this component throughout our React.js
application. The Component class has a number of lifecycle methods which we can
use to manipulate the component from construction to rendering. In our
PublicPage component we use the render method to define what will be
rendered to the DOM. As we did in the index.js file, we wrap the elements that
we want rendered into a single div element because React can only render a
single element at a time. Below is the resulting document when this component
is rendered.

<!doctype html>
<html>
<head>
</head>
<body>
<div id=”root”>
<div>
<h1>This is a public page, everyone has access to this page!
</h1>
</div>
</div>
</body>
</html>

As you can see, React has taken the contents of our PublicPage component and
injected it into the body of our index.html file. Each component that we add to
our React.js application will render to the browser in this way.

Implementing navigation and routing for our frontend with React Router
At this point, the content of our React.js application consists of an index.js file
containing our main content and a PublicPage.js file containing a component.
The challenge at hand is to build a mechanism to transition between this content
and any future content that we will add. This mechanism will need to provide
means to define routes, to maintain a list of routes, to return the content
available at the route to the caller, to locate and match URLs to routes, and to
remember the history of the user’s requested routes. While we could build this
mechanism ourselves, the React Router library is already available and includes
this functionality. The library offers several packages depending on the type of
application being built, but we install the react-router-dom package since we are
building a web application.

$ npm install react-router-dom

As the index.js file is the entry point to our application, let’s refactor this file to
use React Router.

...
import { BrowserRouter as Router, Switch, Route, Link } from “react-
router-dom”;
import PublicPage from ‘./Components/PublicPage’;

ReactDOM.render(
<BrowserRouter>
<div>
<ul>
<li>
<Link to=’/public’>Public</Link>
</li>
</ul>
<Switch>
<Route path=’/public’>
<PublicPage />
</Route>
</Switch>
</div>
</BrowserRouter>, document.getElementById(‘root’)
);

We wrap the div element in a BrowserRouter component that we import from


react-router-dom which uses the HTML5 History API to keep our UI in sync with
the URL. Within the BrowserRouter component we use a Link component
provided by React Router to define a hyperlink which, when clicked, leads to our
PublicPage component. The Link component provides a multitude of built-in
props ; the key one being to which we pass a string representing the pathname
that the Link should expect to find the content.

When the hyperlink is clicked, the URL is requested and it is the Switch

component that tells React Router which Route component to render depending
on the pathname. The Route component is then responsible for rendering the
appropriate component. In our case, we have placed our PublicPage component
at the pathname ‘/public’ so that any request to http://localhost:3000/public
should return that component.

Building a login component with React.js and axios


Great! Our React.js application now has some content to display and a means to
navigate amongst that content. But currently our content is accessible to
anybody. As we discussed in my previous blog Securing a RESTful API Built with
Node.js, there are cases when we want to restrict access to certain content to a
certain group of individuals. In that blog, we built some middleware and
endpoints to our RESTful API to execute this access control logic. Let’s add a
frontend to that logic for the users of our React.js application. We start by
building a component that allows end users to log in.

$ touch LoginPage.js

This component will provide an interface to communicate with the ‘/auth/login’


endpoint from my previous blog. The interface will be a React component. Let’s
define this component now.

import React, { useState } from ‘react’;


import axios from ‘axios’;
import {useHistory, useLocation} from ‘react-router-dom’;

export const LoginPage = (props) => {


const [email, setEmail] = useState(‘’);
const [password, setPassword] = useState(‘’);
let url = props.baseUrl + ‘/auth/login’;
let location = useLocation();
let history = useHistory();
let {from} = location.state || { from: { pathname: “/” } };

function handleSubmit(event){
event.preventDefault();
axios.post(url, {email, password})
.then((res) => {
document.cookie = ‘my-token=’+res.data.token+’; max-age=60;’;
history.push(from);})
.catch((error) => {
console.log(error);});
};
function handleOnChange(event){
if (event.target.name === ‘email’){
setEmail(event.target.value);
};
if (event.target.name === ‘password’){
setPassword(event.target.value);
};
};
return(
<form onSubmit={handleSubmit}>
<h1>Login to your account:</h1>
<label>Email: </label>
<input onChange={handleOnChange} name=”email”
placeholder=”freddie.mercury@gmail.com” size=”35" value={email}/>
<br/>
<label>Password: </label>
<input onChange={handleOnChange} name=”password”
placeholder=”queen_rox” size=”35" value={password}/>
<br/>
<input type=’submit’ value=’Submit’/>
</form>
);
};

From the definition of the LoginPage component, you can tell that this
component is different from the PublicPage component that we defined in the
Adding content to our React.js application section above. The PublicPage

component is an example of a ‘class component’ while the LoginPage component


is an example of a ‘function component’.

While a class component is stateful and has a number of handy lifecycle methods
provided by React, a function component is just a JavaScript function which
makes it stateless. We can, and do, make the LoginPage component stateful in
the first line of its definition where we invoke a new addition to React as of
version 16.8, i.e. the useState hook. A ‘hook’ is a special function that lets us
hook into React features, e.g. the useState lets us hook into the state feature
which was previously exclusive to class components.

Note: The use of brackets in JavaScript syntax on the left-hand side of the
assignment is called ‘array destructuring’. It provides a more concise syntax for
allocating the results of a function, i.e. the first result of the function on the
right-hand side of the assignment populates the first variable in the array on the
left-hand side and so on.

So our code:

const [email, setEmail] = useState(‘’);

Is equivalent to this code:

const stateArray = useState(‘state’);

const email = stateArray[0];

const setEmail = stateArray[1];

Because the useState hook returns an array of two items, the current value of
the component’s state and a function that we use to update it, our array only
requires two variables. These two variables achieve the same means as
this.state.* and this.setState() that we use in class components.

We use the four variables that we create with the useState hook in our
handleOnChange function. This function takes the input that the user enters into
the email and password fields of the form returned by our LoginPage component
and adds them to the state of the component. This gives us a way to store these
values so that we can submit them to our backend’s ‘/auth/login’ endpoint via
our handleSubmit function.

The handleSubmit function of our LoginPage component does the heavy lifting
for our application. First, it takes the email and password values from the
component’s state and sends them in a POST request to our Node.js backend.
Unfortunately, a React.js application can not natively send HTTP requests so we
need a means to achieve this. We could choose to build this functionality
ourselves using the XMLHttpRequest object that most browsers provide, but we
will use the axios library instead. The axios library enables us to send more
complex requests and is better at asynchronous communication using promises
rather than using events and callbacks. To use the axios library, we first install its
package.

$ npm install axios

We now have access to the post method that axios provides which we use to
send the POST request. If the request is invalid, our handleSubmit function
catches and returns the error. However, if the request is successful, it takes the
response which contains the JSON Web Token authenticating that the user is
who they claim to be, tells the browser to create an HTTP cookie (a.k.a. web
cookie or cookie), and stores the token in that cookie.

Storing data in the browser by setting HTTP cookies with JavaScript


In the Building a login component with React.js and axios section above, we
stored the response from our backend in an HTTP cookie. But what exactly is a
cookie? Conceptually, cookies enable us web developers to give the illusion of
statefulness for the stateless HTTP protocol by simulating awareness of the user
across HTTP requests. In actuality, a cookie is a small piece of data (most
browsers support cookies of up to 4096 bytes in size) that is stored in the browser
and then is sent in subsequent requests to the same server. By default, a cookie
exists for the entire session only expiring when the browser shuts down. To
control the lifetime of the cookie, we set a duration after which the cookie is
removed from the browser’s storage. Cookies can be created either by receiving
them from the server using the Set-Cookie HTTP header or by setting them with
a JavaScript application using the document.cookie property. This property
accepts a single string value which should take the form ‘<cookie-name>=
<cookie-value>; <directive-name>=<directive-value>’. For example, in the
handleSubmit function of our LoginPage component we create a cookie named
my-token and give it the value of the JSON Web Token from the response
returned from our backend. In that cookie, we also set the Max-Age directive to
60 which means that the lifetime of our cookie will be 60 seconds.

Securing our routes with React Router


In the Building a login component with React.js and axios section above, we
built a mechanism to fetch the JSON Web Token generated as a result of our
request to our backend’s ‘/auth/login’ endpoint and to store the token in the
browser. In this section, we will use that token to authorize the user to access
different routes and components within our React.js application. We do this
using the components provided by the React Router library. We start by updating
our index.js file where we have initialized one such component, i.e. the
BrowserRouter component, which is the parent component for all of our routes.

...
const BASE_URL = ‘http://localhost:4000';
...
ReactDOM.render(
<BrowserRouter>
<div>
<ul>
<li>
<Link to=’/public’>Public</Link>
</li>
<li>
<Link to=’/private’>Private</Link>
</li>
</ul>
<Switch>
<Route path=’/public’>
<PublicPage />
</Route>
<Route path=’/login’>
<LoginPage baseUrl={BASE_URL}/>
</Route>
<PrivateRoute path=’/private’>
<PrivatePage />
</PrivateRoute>
</Switch>
</div>
</BrowserRouter>
...

Within the Switch component we add a new Route component that will display
our LoginPage component when its route is requested. Next, we add a new
custom PrivateRoute component and define its route. This is a higher-order
component (HOC) that we will reuse to gate all of our private content. Let’s
define this new component now.

$ touch PrivateRoute.js

We then define it.

import React from ‘react’;


import { Route, Redirect } from ‘react-router-dom’;
import isAuthenticated from ‘./isAuthenticated’;

function PrivateRoute(props){
return(
<Route render={() => {
if(isAuthenticated()) {
return(props.children);
} else {
return(
<Redirect to={{pathname: ‘/login’, state: {from:
props.location}}}/>
);
}
}}/>
);
};

export default PrivateRoute;

Like our LoginPage component from the Building a login component with
React.js and axios section above, our PrivateRoute component will also be a
‘function component’ that only takes props as an argument. In addition to being
a function component, our PrivateRoute component is also a higher-order
component. In React.js terminology, a higher-order component is a function that
takes a component as an argument and returns a new component, and that is
precisely what our PrivateRoute component is doing. In the Implementing
navigation and routing with React Router section above, we just nested the
component that we wanted rendered inside the Route component. However, the
Route component also has a property called render which takes a function as
argument. We use this function to execute logic to determine what component to
render: the component with our private content if the user is authenticated or
our login component if the user is unauthenticated. We have access to the
component with our private content via the props argument.

Within props is a property called children which contains any React elements
nested inside the component. Recall from earlier in this section that we nested a
component called PrivatePage within our PrivateRoute component.

...
<PrivateRoute path=’/private’>
<PrivatePage />
</PrivateRoute>
...

So, pending the results of an isAuthenticated function which we will define later
in this section, our logic within render tells our higher-order component to either
return our PrivatePage component or a Redirect component.

The Redirect component is provided by the React Router library and handles
more complex navigation. We can pass an object to this component’s to property
to define additional information like the pathname to redirect to, any query
strings that we want to include as parameters in the generated URL, and a state
to pass from the Redirect component to the component at the end of the path.
We provide the pathname property the path of our LoginPage component which
we defined as ‘/login’ earlier in this section.

Next, we provide the state property an object where we define a from property
as a pointer to the props.location object. In addition to a children property,
props also has a property called location which is an object that contains
information about the current URL that the user is on. We pass this information
to the Redirect component which in turn will pass it via the state property to
the component at the end of the path. In this case, that component is the
LoginPage component.

Recall the following code from the Building a login component with React.js
and axios section above where we defined our LoginPage component.

...
let location = useLocation();
let history = useHistory();
let {from} = location.state || { from: { pathname: “/” } };
...
history.push(from);
...

This code determines where the user is directed to from our LoginPage

component after logging in. To do this, we use the useLocation hook and the
useHistory hook provided by React Router. Calling the useLocation hook
returns an object with a pathname property that represents the path that the user
is currently on and a state property that represents the path that the user was
on right before the current URL.

Note: The use of braces in JavaScript syntax on the left-hand side of the
expression is called ‘object destructuring’. It provides a more concise syntax for
allocating the properties within an object, i.e. the variable inside the braces on
the left-hand side of the assignment will equal the value of the property with the
same name of the object on the right-hand side of the equation.

So our code:

let {from} = location.state || { from: { pathname: “/” } };

Is equivalent to this code:

let from = location.state.from || { from: { pathname: “/” } };

Both segments of code will assign the variable from either indirectly to the value
of from within the state property of the location object if that value is truthy or
directly to an object literal.

So we know where we want to send the user, but we still need a means of
sending them there. We achieve this with the useHistory hook which, when
called, returns a history object that we can use for navigation. The history

object comes from the history package which was installed when we installed the
React Router package. This package provides a number of properties and
methods for managing session history in JavaScript including the push method.
This method takes a string representing the path to the new component, pushes
that as a new entry onto the “history stack”, and then loads the newly pushed
URL.

Note: The history stack is a stack of URLs. Recall from your Computer Science
days that a stack is a data structure in which the first data at the top of the stack
is the last data that was added to the stack. In the case of the history stack, we
are able to use the history object to push new URLs onto the top of the stack and
pop the last URL added from the top of the stack. This implementation of the
stack data structure makes it quick and easy for us to build navigation into our
web application.

By combining the useLocation hook and the useHistory hook provided by React
Router, we now know from which path the user ended up on the LoginPage

component and are able to direct the user accordingly once they are logged in. In
this scenario, the Redirect component will populate the state property for the
LoginPage component so that when the login form returned by our LoginPage

component is submitted the user will be redirected to the path that they were
trying to access, i.e. the ‘/private’ path.

So how will our PrivateRoute component know where to direct the user? This is
the job of the aforementioned isAuthenticated function which we will define
now. First, let’s create the file from which we will import this function.

$ touch isAuthenticated.js

The isAuthenticated function determines whether the user has access to the
content that they are requesting.

import getCookie from ‘./getCookie’;

function isAuthenticated() {
var checkCookie = getCookie(‘my-token’);
if(checkCookie != null){
return true;
} else {
return false;
};
};

export default isAuthenticated;

Our isAuthenticated function is pretty simple. It scans the HTTP cookies


currently stored in the browser looking for a cookie with the name of my-token

using a function called getCookie which we will define in the next section.
Depending on the results of the getCookie function, our isAuthenticated

function will either return true or false which our PrivateRoute component
will pick up and use to determine which component to return.

Retrieving data from the browsers by getting HTTP cookies with JavaScript
The isAuthenticated function defined in the Securing our routes with React
Router section above relies on a getCookie function. In this section, we define
that function. First, let’s create the file from which we will import this function.

$ touch getCookie.js

The getCookie function scans the HTTP cookies stored in the browser to find one
of a particular name. If that cookie exists, the function returns the value
associated with the cookie of that name. If that cookie does not exist, the
function returns a null value.

function getCookie(name){
var cookieArray = document.cookie.split(‘;’);
for(var i=0; i < cookieArray.length; i++){
var cookieKeyValuePair = cookieArray[i].split(‘=’);
if(name === cookieKeyValuePair[0].trim()) {
return(cookieKeyValuePair[1]);
};
};
return(null);
};

export default getCookie;

Recall from the Storing data in the browser by setting HTTP cookies with
JavaScript section above that JavaScript provides the document.cookie property.
Previously, we used this property to set a cookie, but it can also be used to get
cookies from the browser. These cookies are all returned as a single string
following the pattern ‘ <cookie-name>=<cookie-value>;’ We could write a
regular expression that parses through this string to find our cookie, but luckily
JavaScript has provided an easier method, the split method, to split a string
into an array of substrings based on the provided delimiter and return that array.
We store a reference to that array as a new cookieArray variable.

Now it’s a matter of looping through this array to find the element that we seek.
To do this, we split cookieArray into its own array, and store a reference to the
new array as a new cookieKeyValuePair variable. This array will only ever have
two elements consisting of ‘<cookie-name>’ at the index of 0 and ‘<cookie-
value>’ at the index of 1. It’s then a matter of comparing the value at the index
of 0 with the name that’s been provided to getCookie as an argument and when
a match is found, returning the value at the index of 1. Observe the use of the
trim method, also provided by JavaScript which I highly recommend to use as
there is hidden whitespace between the ‘;’ delimiter character between cookies
which must be trimmed in order to find a match.

Note: For those of you interested in ‘Big O Notation’, the algorithm that I have
used for this function has a runtime of O(n). This makes it a ‘linear-time
algorithm’ which will grow with the number of items in the array in the worst
case scenario.

To put all of this into the context of our React.js application, our isAuthenticated

function calls our getCookie function and passes to it ‘my-token’ which is what
we named the cookie in the Building a login component with React.js and
axios section above. The getCookie function checks whether this cookie exists
and returns the value of the cookie which is the value of the JSON Web Token set
in our LoginPage component. Our isAuthenticated function checks this value,
returns true , and the user is granted access to the private content secured by our

PrivateRoute higher-order component.

To secure additional routes, we can simply add each path to Switch as a new
PrivateRoute higher-order component.

...
<Switch>
...
<PrivateRoute path=’/private’>
<PrivatePage />
</PrivateRoute>
<PrivateRoute path=’/superprivate’>
<SuperPrivatePage />
</PrivateRoute>
...
</Switch>
...

Any request to these paths will also redirect the user to our LoginPage

component if they are not logged in.

Securing requests to the backend


In my previous blog Securing a RESTful API Built with Node.js, access control
over the content of the application was all handled by our Node.js application in
the backend. In this blog, we have shifted that responsibility to our React.js
application in the frontend. We used React Router to define the routes for the
components providing the content of our frontend, and we secured navigation
amongst these routes and access to these components by limiting certain routes
to authorized users. This is excellent, however we still want to apply some access
control to various endpoints exposed by our Node.js backend application so that
only users with a valid JSON Web Token can successfully submit requests. We
will do this by adding access control to our ‘/items’ endpoint which we call in the
PrivatePage component. Let’s define this new component now.

$ touch PrivatePage.js

We then define it.

import React from ‘react’;


import axios from ‘axios’;
import getCookie from ‘./getCookie’;

export default class PrivatePage extends React.Component{


constructor(props){
super(props);
this.state = {
items: []
};
};
updateState = () => {
axios.get(‘http://localhost:4000/items')
.then((res) => {
this.setState({items: res.data});})
.catch((error) => {
console.log(error);
});
};
componentDidMount(){
this.updateState();
};
render(){
return(
<div>
<h1>You have successfully logged in. Now, you have access to
this page!</h1>
<h2>List of items:</h2>
<ul>{this.state.items.map((item) => (
<li key={item._id}>{item.name}</li>
))}</ul>
</div>
);
};
};

This component sends a GET request to the ‘/items’ endpoint that we defined in
our Node.js application in order to fetch the data to populate the elements in the
returned list. The response from our Node.js application depends on our route.

...
app.route(‘/items’)

.post(addItem)

.get(getItems);
...

Coded this way, our backend would respond with a status code of 200 OK and
the requested data for any request to this route.

But this means that any client that can send HTTP requests could send a GET
request to our ‘/items’ endpoint and receive this data. That is why in Building a
RESTful API with Node.js we added the verifyToken middleware function to the
route.

...
app.route(‘/items’)

.post(addItem)

.get(verifyToken, getItems);
...

Without any changes in our React.js application, our backend would now
respond with a status code of 403 Forbidden and an error message to our request
to this route.

The good news is that this means that our verifyToken middleware function is
working, and our backend now expects a JSON Web Token to be provided. To get
this working again, we need to add the token to the segment of our code in the
frontend where we send the GET request to the ‘/items’ endpoint. This code
exists in our updateState function.

...
updateState = () => {
let token = getCookie(‘my-token’);
axios.get(‘http://localhost:4000/items', {headers: {‘x-json-web-
token’: token}})
.then((res) => {
this.setState({items: res.data});})
.catch((error) => {
console.log(error);
});
};
...

We use the getCookie function that we defined in the Retrieving data from the
browsers by getting HTTP cookies with JavaScript section above to retrieve
the token that is stored in the browser’s cookies from when the user logged in.
We then pass a new parameter to the post method provided by the axios library
which inserts a new HTTP header into our request with the provided name and
the value of the token returned by the call to our getCookie function. We provide
the new header with the name x-json-web-token because that is the name we
told our verifyToken middleware function to find the token. Now when our
frontend submits the GET request it receives a response with a status code of 200

OK and the requested data again!

Notice that the Request Headers section has a new entry with our x-json-web-

token header and the value of the token.

We can reuse this paradigm across our frontend for future requests to the
backend where we want to secure the request with token verification. In fact, we
could even refactor our React.js application to create a function for this paradigm
that we can import and call from anywhere in our frontend, but that is a subject
for another blog!

Establishing CORS compliant communication between our React.js


frontend and our Node.js backend
If we started our React.js application and tried to log in using the LoginPage

component our browser will throw an error. The error itself will vary by browser.

//Google Chrome
Access to XMLHttpRequest at ‘http://localhost:4000/auth/login' from
origin ‘http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn’t pass access control check: No
‘Access-Control-Allow-Origin’ header is present on the requested
resource.

//Mozilla Firefox
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:4000/auth/login. (Reason:
header ‘content-type’ is not allowed according to header ‘Access-
Control-Allow-Headers’ from CORS preflight response).

We see this error because our React.js application whose origin is


http://localhost:3000 is attempting to make a request to fetch data from our
Node.js application whose origin is http://localhost:4000 and therefore the
request has to cross origins. This is a breach of the Cross-Origin Resource Sharing
(CORS) standard which only allows requests to be sent across origins if the
request follows the CORS standard. Most modern browsers adhere to the CORS
standard, so we should as well. What we need to do is instruct our backend to
allow cross-origin requests. We make this change by adding a few lines of code in
the index.js file of our backend.

...
app.use(function(req, res, next) {
res.header(“Access-Control-Allow-Origin”, “*”);
res.header(“Access-Control-Allow-Headers”, “Content-Type”);
next();
});
...

We pass a function as middleware that sets two headers on the response enabling
our backend to share resources with our frontend. The first header is the Access-

Control-Allow-Origin header which allows our backend to specify how its


resources are shared with external domains. Setting the value of this header to *

means that our backend can share resources with any domain on the internet.
The second header is the Access-Control-Allow-Headers header which allows our
backend to specify which HTTP headers it will accept in the request. When
sending an HTTP request, the browser first sends a ‘preflight’ request using the
OPTIONS HTTP method that lets the backend server know which HTTP headers
might be sent in the actual request. The backend responds to this preflight
request with a preflight response. The preflight request contains the Access-

Control-Request-Headers header, therefore we must set the Access-Control-

Allow-Headers header on the preflight response correspondingly. So let’s take a


look at our preflight request.

Host: localhost:4000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:76.0)
Gecko/20100101 Firefox/76.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Referer: http://localhost:3000/login
Origin: http://localhost:3000
Connection: keep-alive

The addition of the two headers in our index.j s file will generate the following

preflight response.

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type, x-json-web-token
Allow: POST
Content-Type: text/html; charset=utf-8
Content-Length: 4
ETag: W/”4-Yf+Bwwqjx254r+pisuO9HfpJ6FQ”
Connection: keep-alive

Now if we started our React.js application and tried to log in using the LoginPage

component, the request would be successful because we have made the


connection between our frontend and backend CORS compliant!

Running our React.js application


We are finally ready to run our frontend and backend applications! Running the
React.js application that we built in this blog will be different than running the
Node.js application that we built in my previous blogs. Our Node.js application
ran on an HTTP server that we built using the Express.js web framework so that
our application could accept HTTP requests, find the requested document, and
send an HTTP response containing the requested document. Our React.js
application will run directly in the browser using the browser’s built in JavaScript
engine, e.g. V8 in Google Chrome, SpiderMonkey in Mozilla Firefox, and
JavaScriptCore in Apple Safari. This means that our React.js application will
need 1) to be optimized to run in a web browser and 2) to be compatible
horizontally across multiple browser vendors as well as vertically along the
various versions of those browsers, i.e. we can’t expect all of our end users to use
the latest version of Google Chrome.

Our React.js application is a single-page application consisting of multiple


JavaScript files that depend on each other. To allow for these files to interact
efficiently in a browser, we use a module bundler to parse through our code, to
map out dependencies as it comes across them, and to bundle our application
together in a way that a browser can understand. One such module bundler is
called Webpack which we can install in our project via the webpack package.

As Webpack parses through our application, it will come across code where we
have used modern JavaScript syntax which became available in the sixth version
of ECMAScript (a.k.a. ES2015 or ES6). Unfortunately support for ES6 is
inconsistent across browsers and browser versions, and, as mentioned earlier in
this section, we want end users of our application to have the same experience
regardless of their browser of choice. To ensure that our application will run
consistently and smoothly across browsers, we need to transpile our code from
ES6 down to a version of JavaScript that is widely supported across browsers.
One tool to perform this transpilation is Babel which we can install in our project
via the babel package.

Now we could set up our own build environment by installing each of the
necessary tools and build a script to tie them all together, or we could install just
the react-scripts package which does all of this for us including installing
babel and webpack as dependencies.

$ npm install react-scripts

The react-scripts package contains a start.js file which, when executed, will
run a script to start our React.js application. We want to run this script via our
command line interface, but JavaScript files are not executable from the
command line. Instead, we use the Node package manager. Throughout this blog
series, we have only used npm to install packages that we want to use into our
project, however npm also provides an interface to work with them. For example,
the npm start command can be used to run scripts specified in our project. The
only catch is that this command looks for these scripts in a file called the
package.json file. Let’s create this file now.

$ touch package.json

And we fill this new file with the relevant information.

{
“name”: “rest-api-node-react”,
“scripts”: {
“start”: “react-scripts start”
},
...

The package.json file has many uses such as managing dependencies, but for our
purposes we use it to interface with the react-scripts package by setting the
start script in the file’s scripts property to react-scripts start . The scripts

property is a dictionary containing script commands that are run at various times
throughout the lifecycle of our application. The key is the lifecycle event (e.g.
start , build , and eject ), and the value is the command to run at that point

(e.g. react-scripts start , react-scripts build , and react-scripts eject ).

Doing this means that when we run npm start the script within the start.js file
will be executed.

Let’s try running our application now.

$ npm start

If everything is working as expected, we should see an error. This is because


react-scripts is an opinionated library that expects files in our application to be
in certain places. Specifically, it expects to find our index.html file in a public/

directory and our index.js file and associated components in a src/ folder. Let’s
create these directories.

$ mkdir public
$ mkdir src

Once we have moved the files into the appropriate directories, let’s try running
our application again.

$ npm start

It works! Under the covers, the start.js script invokes Webpack to parse
through the application. Webpack starts parsing at src/index.js and loads any
imported modules until it has a complete dependency graph. When it comes
across ES6 code, Webpack passes these files to Babel to convert the code into a
version of JavaScript that will behave consistently across browsers. Webpack
uses the dependency graph to generate a single JavaScript file consisting of our
application’s source code, injects the file via a script tag into public/index.html ,

and starts a development server available at http://localhost:3000. We can see


this injected JavaScript file using our browser’s Developer Tools under the
Sources section.

We now have a frontend built with the React.js library that secures our Node.js
backend from unauthorized requests!

So to retrospect on the work that we did in this blog, we first restructured the
project that we have been working on throughout my previous blogs Building a
RESTful API and Securing a RESTful API Built with Node.js to clearly delineate
our frontend code from our backend code and migrated all of the frontend code
from that blog according to this new structure. We then built out our frontend
starting with a basic React.js application and adding components to present
publicly accessible content. Next, we added new components to present private
content only accessible to authorized users and built a login component to
enable users to authorize themselves. We then added navigation between our
public and private content using the React Router library and secured all routes
to private content against unauthorized users by redirecting them to our login
component. Finally, we enabled communication between the frontend and the
backend using the axios library and secured that requests to the backend so that
only authorized users could access the data protected in our backend via
middleware.

To get some hands on experience building a secure frontend with a single-page


application using React.js, React Router, and axios, clone the source code from
my GitHub of the application that we built throughout this blog and run it on
your local machine.

About the author


Colin Kraczkowsky recently returned to web development after exploring the
craft of product management. Colin’s professional history includes working in
both enterprise and start up environments to code web and mobile applications,
launch new products, build mockups and prototypes, analyze metrics, and
continuously innovate.

In his spare time, Colin can be found checking out the latest Alite camp kit for a
weekend away in Big Sur, planning his next line down a mountain at Kirkwood,
or surfing the Horror section on Netflix. Colin is currently located in San
Francisco, California.

Connect with Colin — https://www.linkedin.com/in/colinkraczkowsky

67 2

Sign up for Top Stories


By Level Up Coding

A monthly summary of the best stories shared in Level Up Coding Take a look.

Get this newsletter

More from Level Up Coding Follow

Coding tutorials and news. The developer homepage gitconnected.com &&


skilled.dev && levelup.dev

Eric Kleppen · May 15, 2020

The Easiest Way to Host a Multi-page Dashboard


using Python, Dash, and Linux for Beginners
Hosting a Dash app on CentOS using uWSGI and Nginx — Why Host a
Dashboard? One of the aspects I enjoy most about data analysis is sharin…

Programming 9 min read

Share your ideas with millions of readers. Write on Medium

Nivan Gujral · May 15, 2020

Artificial Intelligence: How does the Viola-Jones


Algorithm help in object detection?
All humans detect many objects on a daily basis such as cars, trees, and
even other humans. Detecting objects are really common for us to do but…

Artificial Intelligence 6 min read

Akshit Zaveri · May 14, 2020

How To Remove Old Simulators From Xcode?


Save some space by deleting old simulators — As responsible iOS
developers, we often need to test the apps on at least the previous one iOS.
And for that, we goto Preferences ➡ Components and download the iOS…

Xcode 1 min read

Semyon Kirekov · May 14, 2020

Stop Using Setters


Setters-pattern is one the one most popular in lots of programming
languages. Engineers have been using it for such a long time that this
approach has become obvious. Even IDE’s now can generate setters for yo…

Java 3 min read

Kartik Nair · May 14, 2020

Generating accessible color combinations for the


web
Creating a unique experience for every visitor with generative design —
Hey everyone! This is going to be a short post, but I wanted to show you
how easy it is to have the color scheme for your website be generated…

Webdev 4 min read

Read more from Level Up Coding

Recommended from Medium

Joshua Markasky Esteban Ibarra

Angular: When you should use Daily Progress: Making the


Subscribe player models hands stick to
the gun.

Hemantkumar Yahya Sahaja

Download Gmail Attachments Isomorphic Pokemon (Code


using Chrome extension Part 2 Splitting, Lazy Loading, React
Router 4, SPA, SSR) (Part 2)

Maya Alexandera Steve Mu

passportJS and Google OAuth Migrate a JavaScript Nodejs


project to TypeScript

Iskander Samatov in ITNEXT Paschal in HackerNoon.com

ES6 Iterators and Generators — Effects of Redis on an API


when are they actually useful? Server

About Help Terms Privacy

You might also like