Create React App Authentication with Auth0

Authenticate clients with Auth0

Fri, 02 Nov 2018

Create React App Authentication with Auth0

This is something that took me a while to figure due to there being a lot of small caveats. Once I knew exactly where to place all the code and what settings to use it was a breeze to setup. I’m going to make this as brief as possible and teach you how to add protected routes as well as protected components using Auth0.

Packages

Auth0 makes it convenient to get their authentication up and running. We only need one package to use their auth service

yarn add auth0-js

npm install auth0-js

Creating an Auth0 account

Head over to Auth0 to sign up for a free account. After creating your free account you should be prompted to create what is called a tenant. If you’re not prompted to create a new tenant you can click your account name at the top right and then click create tenant.

Screen Shot 2018-11-01 at 2.54.24 PM

Auth0 has a generous free tier. I wouldn’t worry about it while developing your app. It is also scalable.

Think of auth0 tenant’s as a domain. You can have multiple apps/websites under one domain and they would all be sharing the same user database. An example, I developed 3 separate applications for my company under one tenant. Person A can use her same username and password for all 3 websites due to them being under the same tenant.

You can name your tenant anything you’d like. I would name it something similar to your app or the name you’re using to develop it. Remember, you can’t change it once named.

Creating Auth Application

Now that we have our auth0 account we want to start to create a new auth application. Click Applications on the side menu and then click Create Application

Enter in any name for your application and make sure to select Single Page Web Applications. After hitting okay, you should be redirected to the application page for the app you just created. If you not you can do the following

  1. Click on Applications in the side menu
  2. Click on your application name
  3. Click on Settings

If everything worked properly you should have a page similar to this.

Screen Shot 2018-11-01 at 3.22.45 PM

I’d suggest copying your Client ID and Domain to a text editor. We will be using these later.

Now we need to add our Callback URLs in. The Callback URL is a trusted URL for your application. Auth0 redirects the client to this URL once authentication is completed. If the client is redirected to a URL that isn’t on this list the authentication will fail.

  1. Scroll further down to the section Allowed Callback URLs
  2. Since are using the default port and IP address for Gatsby, enter - http://localhost:8000/callback
  3. Scroll to the bottom of the page and click save changes

Setting up env files

We’re going to be putting all of our secrets within env files at the root of our project directory.

  1. Add .env to your projects root directory. The contents of the file should look similar to this.

    Auth_Callback=http://localhost:8000/callback
    Auth_Domain=**insert domain name**
    Auth_ClientId=**insert client id*
  2. Optional - You can create a similar file with .env.production using the same info as the other env file. The only difference is that you want to change your callback url to your production url.

Auth.js

Our Auth file is where all the magic happens. I’ll post the entire files contents and break it down piece by piece.

import auth0js from 'auth0-js';
import history from '../history';

const isBrowser = typeof window !== 'undefined';

export default class Auth {
  auth0 = new auth0.WebAuth({
    domain: process.env.Auth_Domain,
    clientID: process.env.Auth_ClientId,
    redirectUri: process.env.Auth_Callback,
    responseType: 'token id_token',
    scope: 'openid profile email'
  });

  constructor() {
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
    this.getAccessToken = this.getAccessToken.bind(this);
    this.scheduleRenewal()
  }

  login() {
    this.auth0.authorize();
    this.scheduleRenewal();
  }

  logout() {
    // Clear access token and ID token from local storage
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('expires_at');
    localStorage.removeItem('user');
    clearTimeout(this.tokenRenewalTimeout);
    history.replace("/")
  }

  handleAuthentication() {
    if (typeof window !== 'undefined') {
      this.auth0.parseHash((err, authResult) => {
        if (authResult && authResult.accessToken && authResult.idToken) {
          this.setSession(authResult);
          history.replace("/")
        } else if (err) {
          history.replace("/")
          console.log(err);
        }
      });
    }
  }

  isAuthenticated() {
    // Check whether the current time is past the
    // access token's expiry time
    let expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return new Date().getTime() < expiresAt;
  }

  setSession(authResult) {
    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('expires_at', expiresAt);
    this.auth0.client.userInfo(authResult.accessToken, (err, user) => {
      localStorage.setItem('user', JSON.stringify(user));
      history.replace('/');
    })
  }

  getUser() {
    if (localStorage.getItem('user')) {
      return JSON.parse(localStorage.getItem('user'));
    }
  }

  getUserName() {
    if (this.getUser()) {
      return this.getUser().name;
    }
  }

  getAccessToken() {
    const accessToken = localStorage.getItem('access_token');
    if (!accessToken) {
      throw new Error('No access token found');
    }
    return accessToken;
  }
}

auth0

  auth0 = new auth0.WebAuth({
    domain: process.env.Auth_Domain,
    clientID: process.env.Auth_ClientId,
    redirectUri: process.env.Auth_Callback,
    responseType: 'token id_token',
    scope: 'openid profile email'
  });

  constructor() {
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
    this.getAccessToken = this.getAccessToken.bind(this);
    this.scheduleRenewal()
  }

This snippet creates a new auth0 object for us to authenticate against. The constructor binds the functions within this class to its instance.

Login/Logout

The login function starts the authentication method and the logout function removes all Auth0 authentication data from the browser and returns the user back to the page of your choosing. We will be implementing these functions into buttons/links within our app.

handleAuthentication

When the login button is pressed, auth0 will redirect the client to their authentication page. After the user puts their information in they are redirected to the callback url. The callback url will then run the handleAuthentication function and attempt to authenticate the user. If the attempt is successfully the function will attempt to set up a new session for the user.

setSession

If authentication is successful we will store the users token and expiration time in the browser.

isAuthenticated

isAuthenticated is a function that will return either true or false. If the user is currently authenticated, the return value will be true, if not it will be false. We will be using this function to protect our routes.

getUserAccessToken

This function returns the users authentication token. This is useful if you were to implement protected API routes on your backend. I have a full tutorial here that describes the topic in more depth

getUserInfo

Returns the authenticated users email address.

Callback Component

You want to create a new file in your src/views directory named callback.js. The callback component will handle the applications authentication when a user attempts to log in.

import React, { Component } from 'react';
import loading from 'assets/images/loading.svg';

class Callback extends Component {

  render() {
    return (
      <div style={{
        position: 'absolute',
        display: 'flex',
        justifyContent: 'center',
        height: '98vh',
        width: '98vw',
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
        backgroundColor: 'white',
      }}
      >
        <img src={loading} alt="loading" />
      </div>
    );
  }
}

export default Callback;

This component is displayed in the browser while the client is waiting to be authenticated.

While the client is loading we want to show them an indicator so they know everything is working as it should be. The div just centers a loading svg horizontally and vertically. You can display anything you’d like in the return statement of the component, I just think the loading image already a sufficient method. You can create your own loading svg by using this website.

Protect Components

Next we’re going to display components based on whether the user is authenticated or not. This will be a basic use showing how to protect a component. You can extend this example however you see fit using the same logic. This is our index page on our example. Put the following code inside of src/views/index.js

import React from 'react';
import { isAuthenticated } from 'auth/Auth';
import Login from 'components/Login';
import Logout from 'components/Logout';

class IndexPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      authenticated: false,
    };
  }

  componentDidMount() {
	  const { isAuthenticated } = this.props.auth;
    this.setState({ authenticated: isAuthenticated });
  }

	render() {
		const { authenticated } = this.state;
		
		return (
			<div>
				{authenticated && (
            		<Logout/>
          	)}
          	{!authenticated && (
            		<Login />
          	)}
			</div>
		)
	}

}

We haven’t created our Logout and Login components yet, once we do this page will display a login button if the user isn’t authenticated or a logout button if the user is authenticated. When the component is rendered, it will run the isAuthenticated function to see if the client has been authenticated. We set the authentication status to false initially due to us not wanted to display any information until we’re sure the user is authenticated.

Login Component

This component will display a button that will start the authentication process when clicked

import React, { Component } from 'react';
import { Login } from 'auth/Auth';

class LoginButton extends Component {
  constructor(props) {
    super(props);
    this.login = this.login.bind(this);
  }

  login() {
    Login();
  }

  render() {
    return (
        <button onClick={this.login}>
        	Log In
        </button>
    );
  }
}

export default LoginButton;

Logout Component

This component will display a button that will start the logout process when clicked

import React, { Component } from 'react';
import { Logout } from 'auth/Auth';

class LogoutButton extends Component {
  constructor(props) {
    super(props);
    this.logout = this.logout.bind(this);
  }

  logout() {
    Logout();
  }

  render() {
    return (
        <button onClick={this.logout}>
        	Log In
        </button>
    );
  }
}

export default LogoutButton;

Router

You want to create and add the auth object to all of your page components in your router.

import Auth from 'auth/Auth';
import history from './history';
import React from "react";
import ReactDOM from "react-dom";
import Callback from 'views/Callback';
import Index from 'views/Index';

const auth = new Auth();

const handleAuthentication = ({location}) => {
  if (/access_token|id_token|error/.test(location.hash)) {
    auth.handleAuthentication();
  }
}

ReactDOM.render(
    <Router history={history}>
      <Switch>
        <Route path="/callback" render={(props) => {
            handleAuthentication(props);
            return <Callback {...props}/>
          }}/>
        <Route path="/" render={(props) => <Index auth={auth} {...props}/>}/>
      </Switch>
    </Router>, document.getElementById("root"));

Putting it altogether

Once you create all the components, you should be able to test everything out! Clicking on the login button should take you to auth0’s authentication site for your application. Once there you can create a new account from the form that appears. Once you create the account auth0 should redirect you back to your index page. Instead of showing a button to login, now you will see the logout button.

Extending Authentication

You can extend the authentication with the following methods

Buy Me A CoffeeDigitalOcean Referral Badge
Loading...
Edward Beazer

Edward Beazer - I just like to build shit. Sometimes I get stuck for hours, even days while trying to figure out how to solve an issue or implement a new feature. Hope my tips and tutorials can save you some time.

DigitalOcean Referral Badge