developers

Building and Securing MobX Apps with Auth0

Learn how to build React apps with MobX and secure them with Auth0.

Jan 21, 202019 min read

TL;DR: MobX is one of the popular state management libraries out there. It is frequently used with JavaScript frameworks such as React and VueJs. Auth0, on the other hand, is an identity management solution for both web and mobile apps. In this article, you will learn how to build and secure MobX apps with Auth0. The fully functional app can be found on this GitHub Repository.

Prerequisites

You are expected to have prior knowledge of building web apps with React and state management with MobX. If you still need to learn a thing or two about that, here is a good resource to get you started. Asides that, you need Node.js and NPM installed on your machine. If you don't have that, follow the links to set it up on your machine.

Introduction

State management is one of the major talking points of software applications, and React apps are not exempt. It is one of the many functionalities that come packed with React by default. Little wonder React is even referred to as a state management library by some. Because of how important state management is, a lot of solutions have sprung up in an attempt to make it easier. MobX is one of three popular options out there to help manage your state in React apps. Other options include React Context API and Redux.

MobX uses a more reactive approach to state management. Some of the core concepts of MobX includes the following:

  • Observable state: With this concept, you can make an object emit new changes when they are updated. They are usually represented with the
    @observable
    decorator.
  • Observers: The observers are notified of changes made on the observable state. And so the observable state and observers work hand in hand.
  • Computed values: Computed values are used to derive values from an observable state. Let's say you have a function that works with an object marked as
    @observable
    . Anytime the object changes, the function will be automatically computed and the value derived. Computed values are usually represented with the
    @computed
    decorator.
  • Actions: These are functions that modify state. Typically, you will use actions for functions that modify observables. There are represented with the
    @action
    decorator.
  • Reactions: MobX reactions are similar to computed values. But instead of producing a new value, a reaction simply triggers a side effect (side operation). There are three types of reaction functions -
    when
    ,
    autorun
    , and
    reaction
    .

MobX is not just a React library; it is also compatible with other JavaScript libraries and frameworks that power the frontend of web apps. If you are a little short in the knowledge of MobX, you can make do with this resource and the official docs.

MobX uses a more reactive approach to state management.

Tweet This

What You Will Build

In this tutorial, you will build a shopping cart. This is a common functionality you see in e-commerce apps where a user can add and subtract items to and from a cart. Implementing a cart involves keeping track of products a user wishes to buy, their respective quantities and prices. You will use MobX to store and manage the cart data in the app. The app will also need users to log in to access the shopping cart, and that's where Auth0 will come in. You will use Auth0 to handle authentication in the app. You can find the entire code used in this article on this repo.

Scaffolding Your React App

One of the fastest ways to bootstrap a React app is via the create-react-app package. You will use this to bootstrap your project. If you are using an old version of

create-react-app
you would have to update it by following the instructions here.

Open a terminal window, move into a directory of your choice and run this command:

npx create-react-app auth0-mobx-app

This will generate a new project named auth0-mobx-app in the directory where you ran the command. This process will take a few minutes.

One of the fastest ways to bootstrap a React app is via the create-react-app package.

Tweet This

Now that you have generated your project, the ideal thing to do next is to install dependencies that you need in the course of developing your app. If you are not in the directory of your MobX app yet, use this command to move into the directory:

cd auth0-mobx-app

After that, go ahead and install the dependencies by running this command:

npm install mobx mobx-react react-router react-router-dom @auth0/auth0-spa-js

This command will install the following dependencies:

  • mobx
    : This is the main MobX library that powers state management.
  • mobx-react
    : This library contains React specific functions available through MobX.
  • react-router-dom
    and
    react-router
    : These libraries will be used to add page navigation to the app.
  • auth0-spa-js
    : This is the Auth0 client-side library.

Setting Up MobX

MobX uses decorators to handle its state management and React doesn't come with support for decorators by default. So you will install a babel plugin

@babel/plugin-proposal-decorators
. Still, on your terminal, run the following commands:

npm install @babel/plugin-proposal-decorators

After installation, you have to eject the React app to be able to configure the babel plugin you just installed. To eject the React app, run the following command on your terminal. Ensure that you commit your code to git before running the command below:

npm run eject

This command ejects some scripts and configs initially inside the

node_modules
to the project folder so you get a project structure as shown below:

View of folder structure for application

After that, add the configuration for the babel plugin you just installed. Create a

.babelrc
file in the project directory and add the following configuration to the file as shown below:

// .babelrc

{
  "presets": ["react-app"],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ]
  ]
}

To avoid conflict with the babel plugin configurations, you need to delete the existing babel configuration in

package.json
. Open the
package.json
file and remove this snippet from the file:

"babel": {
  "presets": [
    "react-app"
  ]
}

Now, you need to create a store for your application. Stores are usually compulsory in MobX apps. One of the main points of having them is that they help you move logic and state out of your components into a standalone testable unit that can be used in both frontend and backend JavaScript. This is usually a good idea if you want to write maintainable and testable apps.

Go ahead and create a

Store.js
file in the
src
folder. After creating the file, paste this snippet inside it:

// src/Store.js

class Store {
    products = [
      {
        id: 1,
        name: 'Tshirt sleeker',
        description: 'Wonderfully fitted',
        price: 50,
        image: require('./img/image-1.jpg')
      },
      {
        id: 2,
        name: 'Tshirt sleeker',
        description: 'Wonderfully fitted',
        price: 350,
        image: require('./img/image-2.jpg')
      },
      {
        id: 3,
        name: 'Tshirt sleeker',
        description: 'Wonderfully fitted',
        price: 250,
        image: require('./img/image-3.jpg')
      }
    ];
    carts = [];
    currentCart = [];
    removeFromCart(id) {
      this.carts = this.carts.filter(item => {
        return item.product_id !== id;
      });
      this.getCart();
    }
    increaseQuantityInCart(id) {
      this.carts.map(item => {
        if (item.product_id === id) item.quantity += 1;
        return item;
      });
      this.getCart();
    }
    decreaseQuantityInCart(id) {
      this.carts.map(item => {
        if (item.product_id === id && item.quantity > 1) item.quantity -= 1;
        return item;
      });
      this.getCart();
    }
    addToCart(id) {
      let found = false;
      this.carts.map(item => {
        if (item.product_id === id) {
          item.quantity += 1;
          found = true;
        }
        return item;
      });
      if (!found) {
        this.carts.push({ product_id: id, quantity: 1 });
      }
      this.getCart();
    }
    getCart() {
      let carts = this.carts;
      carts.map(item => {
        for (let i in this.products) {
          if (item.product_id === this.products[i].id) {
            item.image = this.products[i].image;
            item.name = this.products[i].name;
            item.description = this.products[i].description;
            item.price = this.products[i].price * item.quantity;
          }
        }
        return item;
      });
      this.currentCart = carts;
    }
}
export default Store;

From the snippet above, you created a store that holds the state of the app. Here is a breakdown of what each state object does:

  • products
    • This is an array that will store all the available products a user can add to the cart. Here, there are three hardcoded items in
      products
      .
  • carts
    • This is an array that will store items added to the cart by the user.
  • currentCart
    • This is also an empty array and it is similar to the
      carts
      array. But this holds the initial state of the cart when the user enters the system or logs on to the system.
  • removeFromCart
    • This function will be used to remove an item from the
      carts
      array.
  • increaseQuantityInCart
    • This function will be used to increase the quantity of a particular item in the
      carts
      array.
  • decreaseQuantityInCart
    • This function will be used to decrease the quantity of a particular item in the
      carts
      array.
  • addToCart
    • This function will be used to add a new item to the
      carts
      array.
  • getCart
    • This function returns all the data in the
      carts
      array.

Creating Your Components

In this section, you will build the various components for your app. Before you start building, you need to add Bootstrap and Font Awesome CDN links. You will use them both to style your app.

Open the

public/index.html
file and add these links in the
<head>
tag:

<!-- public/index.html -->

<link
  rel="stylesheet"
  href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
/>
<link
  href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
  rel="stylesheet"
/>

Next, open the

App.css
file and replace its content with this snippet:

/* src/App.css */

.formSection {
  margin-top: 30px;
}
.formSection p {
  font-weight: bold;
  font-size: 20px;
}
.dashboardSection {
  margin-top: 50px;
}
.reviewsWrapper {
  margin-top: 50px;
}
.top-space {
  margin-top: 30px;
}
.price {
  padding: 7.5px;
  border: 1.2px solid #ffc107;
  color: #807e7b;
  width: 100px;
  font-size: 14px;
  font-weight: bold;
  padding-left: 15px;
  padding-right: 15px;
  border-radius: 3px;
}
.detail {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}
.product-cart-name {
  font-size: 12px;
}
.control > .btn {
  padding: 0px 5px;
  font-size: 11px;
  margin-right: 5px;
  height: 20px;
}
.quantity {
  font-size: 14px;
}
.cart {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin: 10px;
}
.cancel {
  padding: 0px 3px;
  font-size: 10px;
  line-height: 10px;
  height: 20px;
}
.login {
  background-color: #353535;
  padding: 40px;
  margin-top: 100px;
  text-align: center;
  height: 350px;
  display: flex;
  align-items: center;
  justify-content: center;
}

The above snippet is the custom styling applied throughout the app to make it look decent.

As mentioned earlier, this application is geared towards building a shopping cart, and so you need some mock data to play with. Here, you need to add some images to the project. Create a folder called

img
in the
src
folder and add three images, namely:
image-1.jpg
,
image-2.jpg
, and
image-3.jpg
. Better still, you can use the images in the repository.

After setting up the app CSS style and adding images to your project, you will now proceed to build your components.

The Cart component

Here, you will create the Cart component. This component will be responsible for displaying data in the cart state variable, which you defined in the store. First, create a

components
folder in the
src
folder. Then create a new
Cart.js
file in the
src/components
directory and add the following snippet to it:

// src/components/Cart.js

import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
@inject('store')
@observer
class Cart extends Component {
  render() {
    return (
      <div className='card'>
        {this.props.store.currentCart.map((data, index) => (
          <div key={index} className='cart'>
            <button
              onClick={() => this.props.store.removeFromCart(data.product_id)}
              className='btn btn-default btn-xs cancel'
            >
              X
            </button>
            <img height={30} src={data.image} alt='Product stuff' />
            <div className='product-cart-name'>{data.name}</div>
            <div className='control'>
              <button
                onClick={() =>
                  this.props.store.increaseQuantityInCart(data.product_id)
                }
                className='btn btn-default btn-xs'
              >
                +
              </button>
              <button
                onClick={() =>
                  this.props.store.decreaseQuantityInCart(data.product_id)
                }
                className='btn btn-default btn-xs'
              >
                -
              </button>
            </div>
            <div className='quantity'>{data.quantity}</div>
            <div className='quantity'>$ {data.price}</div>
          </div>
        ))}
      </div>
    );
  }
}
export default observer(Cart);

In this snippet, you coded the Cart component that will display items a user adds to the cart. For each item in the cart, you will display the image, name, description, price, and quantity. These items can be increased, reduced, or removed entirely by calling specific actions defined in the store.

The Product component

Next up, you will build the Product component. The Product component will be responsible for displaying all the items available for the user to add to cart and purchase. Typically, this will be populated by your backend API. But for the sake of this tutorial, they have been hardcoded for you in the store.

Create a new file

Product.js
in the
src/components
directory and add the following snippet to it:

// src/components/Product.js

import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import Cart from './Cart';
@inject('store')
class Product extends Component {
  addToCart(id) {
    this.props.store.addToCart(id);
  }
  list(data, index) {
    return (
      <div key={index} className='col-md-4 top-space'>
        <div className='card'>
          <img
            className='card-img-top'
            height={200}
            src={data.image}
            alt='Product stuff'
          />
          <div className='card-body'>
            <h4 className='card-title'>{data.name}</h4>
            <p className='card-text'>{data.description}</p>
            <div className='detail'>
              <div className='price text-center'>$ {data.price}</div>
              <button
                onClick={() => this.addToCart(data.id)}
                className='btn btn-primary btn-sm'
              >
                Add to Cart
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }
  render() {
    return (
      <div className='row'>
        <div className='col-md-8'>
          <div className='row'>
            {this.props.store.products.map((data, index) =>
              this.list(data, index)
            )}
          </div>
        </div>
        <div className='col-md-4'>
          <div className='top-space'>
            <Cart />
          </div>
        </div>
      </div>
    );
  }
}
export default observer(Product);

Here, you have the Product component that displays all the products available in the store. The Product component is wrapped with a higher-order component (i.e., observer) which automatically subscribes the component to an observable(in this case,

products
) so that this component only re-renders when there is a change in the
products
array.

You also imported and rendered the Cart component defined above to give the user a visual representation of the items in the cart when the user adds or removes an item.

Next, open the

src/index.js
file and replace the default code with this snippet:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

The difference between this snippet and the default one is that now you're wrapping your entire component tree with the

BrowserRouter
imported from the
react-router-dom
package.

Next, replace the code in your

src/App.js
file with this snippet:

// src/App.js

import React, { Component } from 'react';
import Product from './components/Product';
import { Provider } from 'mobx-react';
import { withRouter } from 'react-router';
import { Route } from 'react-router-dom';
import Store from './Store';
import { decorate, observable, action } from 'mobx';
import './App.css';
decorate(Store, {
  products: observable,
  addToCart: action,
  increaseQuantityInCart: action,
  decreaseQuantityInCart: action,
  removeFromCart: action,
  currentCart: observable
});
const shoppingStore = new Store();
class App extends Component {
  render() {
    return (
      <Provider store={shoppingStore}>
        <div className='container'>
          <Route exact path='/' render={() => <Product />} />
        </div>
      </Provider>
    );
  }
}
export default withRouter(App);

In this snippet, you imported the

Store
defined earlier. The decorate utility designates the
products
and
currentCart
as the entity that can be observed by components for changes. While the
addToCart
,
increaseQuantityInCart
,
decreaseQuantityInCart
, and
removeFromCart
functions are used to update the store by manipulating the cart.

Finally, you set the Product component to be the home(

/
) route of the application.

At this point, you can test your app by running this command in the project terminal:

npm run start

You should have something like this:

View of Cart

Securing Your App With Auth0

Auth0 is a modern identity platform for rapidly integrating authentication and authorization for web, mobile, and legacy applications so you can focus on your core business.

To secure the app with Auth0, you need an Auth0 account. Create an account here or login to your account. When you get to your dashboard, click on the orange Create Application button.

You will be presented with a dialog like this:

Create Application Modal view

Enter a suitable name for your app and select Single Page Web Applications as the type. After that, click the Create button.

When your application has been created, you will see a page like this once you click the "Settings" tab:

Settings Tab view in Auth0 application

Go to the Settings tab, scroll to where you have Allowed Callback URLs, add this URL

http://localhost:3000/callback
and click Save Changes at the bottom of the page.

Copy out your ClientID and Domain as you will need them soon.

Now that you have set up your Auth0 app, you will go ahead with the Auth0 implementation in your code. You will begin by adding some state objects in the store. Open the

Store.js
file and add these:

// src/Store.js

class Store {
  // ... Leave the other objects and functions untouched
  loading = true;
  auth0 = null;
  authenticated = null;
  setLoader(loading) {
    this.loading = loading;
  }
  setAuth(token) {
    this.authenticated = token;
  }
  initialize(auth0) {
    this.auth0 = auth0;
  }
}

Here is what the objects and functions you just added do:

  • loading
    • This is used to control loaders which indicates whether a process has completed or not.
  • auth0
    • This is the variable that will hold the instance of the initialized Auth0 client.
  • authenticated
    • This is the variable that will hold the token generated after authenticating a user with
      auth0
      .
  • setLoader
    • This function will be used to alter the state of the loading variable.
  • setAuth
    • This function will be used to set the token generated for a user after authentication with
      auth0
      .
  • initialize
    • This function will be used to update the initialized Auth0 client.

Next, create an

Auth
folder in the
src/components
directory. So, you'll have a directory like this
src/components/Auth
, create an
Auth.js
file there and paste this snippet:

// src/components/Auth/Auth.js

import createAuth0Client from '@auth0/auth0-spa-js';
import {Component} from 'react';
import {inject } from 'mobx-react';
@inject('store')
class Auth extends Component{
  async componentWillMount(){
      let auth0 = await createAuth0Client({
        domain: 'AUTH0_DOMAIN',
        client_id: 'AUTH0_CLIENT_ID',
        redirect_uri: 'http://localhost:3000/callback',
        responseType: 'token id_token',
        scope: 'openid profile'
    });
    this.props.store.initialize(auth0);
    this.props.store.setLoader(false);
  }
  render(){
    return(null);
  }
}
export default Auth;

This class is responsible for bootstrapping Auth0 in the app. It initializes the

auth0
client with the credentials provided and updates it on the store.

Remember to replace the placeholders with your Auth0 credentials from your dashboard. The Auth component will handle all of the logic concerned with authenticating the user.

Building the Login component

Here, you will build a Login component. This component will be responsible for logging the user in. A user won't be allowed to view the Products component unless logged in.

Now create a new

Login.js
file in the
src/components/Auth
folder and paste this snippet in it:

// src/component/Auth/Login.js

import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
@inject('store')
@observer
class Login extends Component {
  render() {
    return (
      <div className='row'>
        <div className='col-md-4 offset-md-4'>
          <div className='login'>
            <button disabled={this.props.store.loading} className='btn btn-primary' onClick={()=> this.props.store.auth0.loginWithRedirect()}>
            {this.props.store.loading ? <i className="fa fa-gear fa-spin"/>: null} Login
            </button>
          </div>
        </div>
      </div>
    );
  }
}
export default Login;

The Login component only displays a login button. Attached to the button is an

onClick
listener that calls the
loginWithRedirect()
method from the
auth0
client. The login button is disabled and shows a loading icon until
auth0
client initialization is complete.

Earlier in your Auth0 dashboard, you set a callback URL as

http://localhost:3000/callback
. So, now you have to create a component that matches the
/callback
path.

Create a new folder

callback
in the
src
folder. Then create a file
Callback.js
in the
src/callback
directory and paste this snippet:

// src/callback/Callback.js

import React, {Component} from 'react';
import { withRouter } from 'react-router';
import { observer, inject } from 'mobx-react';
@inject('store')
@observer
class Callback extends Component{
  async componentWillUpdate(nextProps){
    await this.props.store.auth0.handleRedirectCallback();
    let token = await this.props.store.auth0.getTokenSilently();
    this.props.store.setAuth(token);
    this.props.history.push('/');
  }
  render(){
    return 
      <div>Loading user profile. {this.props.store.loading ? 
        <i style={{color: 'black'}} className="fa fa-gear fa-spin"></i> 
        : null}
      </div>;
  }
}
export default withRouter(Callback);

In this snippet, you are using the

loading
object from the store to track when the
auth0
client has been initialized because of its asynchronous nature. Once the value of the
loading
object changes (signaling the completion of the
auth0
client initialization), the life cycle method
componentWillUpdate
runs. This function handles the callback redirection, stores the token in the store, and redirects the user to the products page.

Now, open your

src/App.js
file. First, add these to the import section:

// src/App.js

import Login from './components/Auth/Login';
import Auth from './components/Auth/Auth';
import Callback from './callback/Callback';

Here, you imported the Login, Auth, and Callback components.

In your

App.js
file, add the
loading
object as an observable like so:

// src/App.js

decorate(Store, {
  // ... Other observables and actions
  loading: observable
});

Then replace the main

App
component with this:

// src/App.js

class App extends Component {
  render() {
    return (
      <Provider store={shoppingStore}>
        <Auth />
        <div className='container'>
          <Route
            exact
            path='/callback'
              render={() => <Callback auth={this.props.auth} />}
          />
          <Route
            exact
            path='/'
            render={() => (
              <Product
                history={this.props.history}
              />
            )}
          />
          <Route
            exact
            path='/login'
            render={() => <Login auth={this.props.auth} />}
          />
        </div>
      </Provider>
    );
  }
}

In this snippet, you rendered these components as routes passing in the

auth
and
history
prop where necessary. You made an update to the Product component by also passing in the
auth
prop. Remember, the goal is to render the Product component only when the user is authenticated.

The Product component is not yet aware of the new prop you passed to it. To fix this open

src/components/Product.js
and add this snippet just before the
addToCart()
method:

// src/components/Product.js

componentWillMount() {
  if (!this.props.store.authenticated) {
    this.props.history.push("/login")
  }
}

This snippet tells the Product component to redirect to the Login component if the user is not logged in.

Testing Your App

Now, run this command again to test your app:

npm run start

Then, this should open a browser tab with the URL -

http://localhost:3000
. If you did everything correctly, you should be redirected to the Login component and prompted to log in:

Login Screen for Application

When the login button is clicked, you'll be redirected to the Auth0 authentication page, which requests you to either login or sign up.

Auth0 Modal View

After a successful login, you'll be redirected to the products page.

View of Cart with three items in it and one item selected

Conclusion

In this article, you learned how to build a shopping cart with MobX for state management and securing the application with Auth0. MobX and Auth0 are very powerful and have a lot of features not covered in this article.

About Auth0

Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.