Auth0 provides many tools to integrate authentication into our applications. When building JavaScript apps in the past, Auth0 has recommended using the auth0.js library. Recently Auth0 has created a new upgraded library to work with authentication in Single Page Apps (SPAs). This new library was released on July 2, 2019 and is called the Auth0 Single Page App SDK.

In this tutorial, we will integrate this Auth0 SPA SDK with the popular JavaScript library, React. We will be building it using React Context and Hooks.

What We'll Build

We'll be building a React application where we can:

  • Use Auth0 to log in via email or social (Facebook, Twitter, Google, and more)
  • Grab user data
  • Grab an authentication access token to call APIs
  • Protect parts of the application using React Router

The most important feature we'll create by the end of this tutorial is Context for state management. With that, we will have a solid foundation for Auth0 React authentication in all our apps.

We can find the GitHub repo here. Feel free to use this demo app as a foundation for authenticated React applications.

Let's get started!

Features of the Auth0 SPA SDK

The Auth0 SPA SDK is a more focused library that specializes in SPAs. It provides many benefits over the auth0.js library:

  • Targeted for SPA use
  • Requires less code for Single Page Apps
  • Smaller size! Comes in at ~7kb
  • Automatically manages token expiration and renewal

The Authentication Flow for End-Users

What will the experience be like for end-users? Since we're going to use the Auth0 SPA SDK, we will be using Auth0's Universal Login.

  1. User clicks login
  2. User sees the Auth0 login page and enters credentials
  3. Auth0 redirects user to the application
  4. The app gets user information from Auth0
  5. Show and hide parts of the app if the user is authenticated

The benefit of using Auth0's Universal Login is that all authentication happens on Auth0's page. This means we don't have to worry about login design.

Starting Our Authenticated React App

Let's start off by creating a brand new React application:

npx create-react-app auth0-react-app

Note: npx is a tool that allows us to run one-time npm commands. It will go grab the latest version of create-react-app. npx is only available in npm v5.1+.

To navigate to the project run:

cd auth0-react-app

We've got our brand new React app! Let's open it in our code editor of choice.

Quick Styles for Our App

For this tutorial, we'll bring in the CSS framework, Bulma. It's a simple way to add some clean styles.

# with npm
npm install --save bulma

# with yarn
yarn add bulma

Once the installation is complete (this may take some time) let's jump into our src/App.js and add in the Bulma styles. We're going to add the CSS with import 'bulma/css/bulma.css'. We'll also remove some of the boilerplate stuff React adds into this file.

We can delete the files: App.css and logo.svg. These will not be used in this tutorial.

We'll add a few classes to make our app look a little bit styled. We're using Bulma's Hero styles.

// src/App.js

import React from 'react';
import 'bulma/css/bulma.css';

function App() {
  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          my app goes here!
        </div>
      </div>
    </div>
  );
}

export default App;

Start our application with npm start and we can see our app at http://localhost:3000.

We will now see in our browser window "my app goes here!" with a blue background!

Let's move to the next step and start working on integrating authentication for our React app using the Auth0 SPA SDK!

How to Use the Auth0 SPA SDK with React

Here is an overview of the steps needed to integrate the Auth0 SPA SDK with React. We can bring these steps to any React application. We'll go through all these steps in this tutorial as we build a sample React application.

  1. Configure the Auth0 account

  2. Install the Auth0 SPA SDK: npm install @auth0/auth0-spa-js

  3. Add it to React (we'll use React Context and React Hooks)

  4. Display authentication data in the React components

"The benefit of using Auth0 Universal Login is that all authentication happens on Auth0's page. This means we don't have to worry about login design."

Configuring Our Auth0 Account

Let's go into the Auth0 Dashboard. To get there, we will want to create an Auth0 account or log in to our account. Once we're in our dashboard, we'll hit the button CREATE APPLICATION. Name the app (for example: React SPA SDK Demo) and make sure to choose Single Page Web Applications. Hit CREATE when finished.

Auth0 Create Application View

We will then hit the Settings tab to see all of our information needed for the application.

Take note of the "Domain" and "Client ID". We will need these soon!

If we scroll down we will find the following fields:

  • Callback URL
  • Allowed Web Origins
  • Logout URLs

In each of these, we need to set them to http://localhost:3000 because our React app works on that URL in development. Once we have entered those values into those three spots, we need to hit SAVE CHANGES at the bottom of the page.

Note: When moving to production, make sure to add the correct URLs here.

Now that we have our application credentials and have configured our callback URL, we can get back to our code and add the Auth0 SPA SDK to React.

Adding the Auth0 SPA SDK to React

Let's get started and install the Auth0 SPA SDK:

# with npm
npm install --save @auth0/auth0-spa-js

# with yarn
yarn add @auth0/auth0-spa-js

We may need to use our auth tools from the SDK in multiple components around our application. We'll use a React Context so that we have access to auth tools all over our application.

Using React Context to Store Authentication Information

We are going to create a new folder in the src folder named contexts. Within that new folder, let's create a file named auth0-context.js. We'll store our Auth0 Context in this file. Let's start our file by creating a simple context and provider. We'll create a single state variable called message. We'll use this to test out our context and make sure it works.

To learn more about React Context, read this blog post!

// src/contexts/auth0-context.js

import React, { Component, createContext } from 'react';

// create the context
export const Auth0Context = createContext();

// create a provider
export class Auth0Provider extends Component {
  state = { message: 'testing message here!' };

  render() {
    const { message } = this.state;
    const { children } = this.props;

    const configObject = { message };

    return (
      <Auth0Context.Provider value={configObject}>
        {children}
      </Auth0Context.Provider>
    );
  }
}

Let's use our new Auth0Provider inside of index.js. We need to wrap our entire application with the Auth0Provider so that we can use its context.

Go into src/index.js and let's wrap everything. We're also going to clean things up by removing the index.css and service worker.

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Auth0Provider } from './contexts/auth0-context';

ReactDOM.render(
  <Auth0Provider>
    <App />
  </Auth0Provider>,
  document.getElementById('root')
);

Now that our entire application is wrapped, let's go into App.js and give our new context a try. Let's see if we can get message to show.

We will import useContext and that will allow us to get access to the message.

// src/App.js

import React, { useContext } from 'react'; // <-- updated
import 'bulma/css/bulma.css';
import { Auth0Context } from './contexts/auth0-context'; // <-- new

function App() {
  const auth0 = useContext(Auth0Context); // <-- new

  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          {auth0.message}
        </div>
      </div>
    </div>
  );
}

export default App;

We have imported useContext and Auth0Context. We've also shown our message "testing message here!" using {auth0.message}. We now know that our context is working! We are able to create some data inside of our Auth0Context and display it inside of a component.

Our data lives inside of our Auth0Provider. We will wrap our App with this context provider like so: <Auth0Provider><App /><Auth0Provider>. After we have wrapped our App, then we can access our data from any child component. We will access this data from a child component using React's useContext.

Let's take this a step further and start using the Auth0 SPA SDK inside of this context.

Putting Our Information in Environment Variables

Let's return to our Auth0 application and grab those values we took note of earlier: Domain and Client ID. We are going to keep these bits of sensitive information safe by putting them into a .env file. We'll use Create React App's way of using environment variables by prefixing our variables with "REACT_APP_".

Create a new file, .env in the root of the project, and input the following information from your Auth0 application:

REACT_APP_AUTH0_DOMAIN={YOUR DOMAIN HERE}
REACT_APP_AUTH0_CLIENT_ID={YOUR CLIENT ID HERE}

Now we have our sensitive information in an .env file. Make sure you don't commit this to your source control and add it to the .gitignore file!

Instantiating the Auth0 SPA SDK

We are going to start up an Auth0 client whenever our app starts.

We will:

  • Use Auth0 SPA SDK's createAuthClient() method
  • Create an initializeAuth0 method where we create an auth0Client
  • Create an auth0Client property on our state
  • Create a config property to store our credentials from Auth0 (Domain and ClientID)

All of this will be done within our Auth0Provider class in our auth0-context.js file.

// src/contexts/auth0-context.js

import React, { Component, createContext } from 'react';
import createAuth0Client from '@auth0/auth0-spa-js';

// create the context
export const Auth0Context = createContext();

// create a provider
export class Auth0Provider extends Component {
    state = {
        auth0Client: null
    };
    config = {
        domain: process.env.REACT_APP_AUTH0_DOMAIN,
        client_id: process.env.REACT_APP_AUTH0_CLIENT_ID,
        redirect_uri: window.location.origin
    };

    componentDidMount() {
        this.initializeAuth0();
    }

    // initialize the auth0 library
    initializeAuth0 = async () => {
        const auth0Client = await createAuth0Client(this.config);
        this.setState({ auth0Client });
    };

    render() {
        const { children } = this.props;

        const configObject = { };

        return (
            <Auth0Context.Provider value={configObject}>
                {children}
            </Auth0Context.Provider>
        );
    }
}

Adding a loading property

We'll add a loading property to our state so that we can wait for authentication to confirm. Whenever a single page app is loaded, our app needs to call the Auth0 API through the Auth0 SDK. This check to the API takes time. While we are waiting to get authentication data from the Auth0 API, we should show that our app is loading. We shouldn't show any user information until we know if they are logged in or not.

We will be updating the state, initializeAuth0, and configObject. Look for the comments "updated" or "new" to see what changed in the code block below.

// src/contexts/auth0-context.js

import React, { Component, createContext } from 'react';
import createAuth0Client from '@auth0/auth0-spa-js';

// create the context
export const Auth0Context = createContext();

// create a provider
export class Auth0Provider extends Component {
    state = {
        auth0Client: null,
        isLoading: true // <-- new
    };
    config = {
        domain: process.env.REACT_APP_AUTH0_DOMAIN,
        client_id: process.env.REACT_APP_AUTH0_CLIENT_ID,
        redirect_uri: window.location.origin
    };

    componentDidMount() {
        this.initializeAuth0();
    }

    initializeAuth0 = async () => {
        const auth0Client = await createAuth0Client(this.config);
        this.setState({ auth0Client, isLoading: false }); // <-- updated
    };

    render() {
        const { isLoading } = this.state; // <-- updated
        const { children } = this.props;

        const configObject = { isLoading }; // <-- updated

        return (
            <Auth0Context.Provider value={configObject}>
                {children}
            </Auth0Context.Provider>
        );
    }
}

Checking authentication on user

We'll keep adding to our state with an isAuthenticated property. We can use this isAuthenticated property across our entire application to check if a user is logged in or not. We can even use this property to hide and show parts of our UI based on if the user is logged in or logged out. We'll use the Auth0 SPA SDK's isAuthenticated() method.

Add an isAuthenticated property to state and we'll add more to initializeAuth0():

// src/contexts/auth0-context.js

// ...imports

export class Auth0Provider extends Component {
  state = {
    auth0Client: null,
    isLoading: true,
    isAuthenticated: false // <-- new
  };

  // ...config and componentDidMount()

  // initialize the auth0 library
  initializeAuth0 = async () => {
    const auth0Client = await createAuth0Client(this.config);
    const isAuthenticated = await auth0Client.isAuthenticated(); // <-- new

    this.setState({ auth0Client, isLoading: false, isAuthenticated }); // <-- updated
  };

   render() {
        const { isLoading, isAuthenticated } = this.state; // <-- updated
        const { children } = this.props;

        const configObject = {
            isLoading,
            isAuthenticated, // <--new
        };

        return (
            <Auth0Context.Provider value={configObject}>
                {children}
            </Auth0Context.Provider>
        );
    }
}

Grabbing an authenticated user

The last part of our initializeAuth0() is to go and grab the user if that user is logged in. Let's add a user property to state and update our method:

// src/contexts/auth0-context.js

// ...imports

export class Auth0Provider extends Component {
    state = {
        auth0Client: null,
        isLoading: true,
        isAuthenticated: false,
        user: null // <-- new
    };

  // ...config and componentDidMount()

  // initialize the auth0 library
    initializeAuth0 = async () => {
        const auth0Client = await createAuth0Client(this.config);
        const isAuthenticated = await auth0Client.isAuthenticated();
        const user = isAuthenticated ? await auth0Client.getUser() : null; // <-- new

        this.setState({ auth0Client, isLoading: false, isAuthenticated, user }); // <-- updated
    };

    render() {
        const { isLoading, isAuthenticated, user } = this.state; // <-- updated
        const { children } = this.props;

        const configObject = {
            isLoading,
            isAuthenticated,
            user, // <-- new
        };

        return (
            <Auth0Context.Provider value={configObject}>
                {children}
            </Auth0Context.Provider>
        );
    }
}

Our Entire auth0-context.js File

Let's take a step back and see the entire auth0-context.js file.

// src/contexts/auth0-context.js

import React, { Component, createContext } from 'react';
import createAuth0Client from '@auth0/auth0-spa-js';

// create the context
export const Auth0Context = createContext();

// create a provider
export class Auth0Provider extends Component {
    state = {
        auth0Client: null,
        isLoading: true,
        isAuthenticated: false,
        user: null
    };
    config = {
        domain: process.env.REACT_APP_AUTH0_DOMAIN,
        client_id: process.env.REACT_APP_AUTH0_CLIENT_ID,
        redirect_uri: window.location.origin
    };

    componentDidMount() {
        this.initializeAuth0();
    }

    // initialize the auth0 library
    initializeAuth0 = async () => {
        const auth0Client = await createAuth0Client(this.config);
        const isAuthenticated = await auth0Client.isAuthenticated();
        const user = isAuthenticated ? await auth0Client.getUser() : null;

        this.setState({ auth0Client, isLoading: false, isAuthenticated, user });
    };

    render() {
        const { isLoading, isAuthenticated, user } = this.state;
        const { children } = this.props;

        const configObject = {
            isLoading,
            isAuthenticated,
            user
        };

        return (
            <Auth0Context.Provider value={configObject}>
                {children}
            </Auth0Context.Provider>
        );
    }
}

We have now created a React context that is a React class. This class acts the same as most React classes. It carries its own state and has its own render() function. This Auth0Provider is also known as a higher-order component. This will wrap our overall <App /> component and will pass down its own state to every component inside of our React app. This is how we will be able to use properties in this Auth0Provider across our entire application.

This Auth0Provider provides us with some important data like:

  • isLoading
  • isAuthenticated
  • user

Now we have access to those in every part of our application! isAuthenticated will be false and user will be null for now.

In an upcoming section, we'll see exactly how to use this provider and context. For now, let's move forward and add functionality for users to log in and sign up.

We'll keep adding to this file to handle important authentication things like:

  • Signup
  • Login
  • Handling the redirect after logging in or signing up

Let's keep moving along! The next step is to add a button to let users sign up from our App.js.

Logging in a User

To log in users, we will be using Auth0's Universal Login. This is a convenient way to log a user in because we don't have to style our own login page. We will be redirecting users to an Auth0 hosted login page (Universal Login) and then that page will redirect our user back to our application. To start logging users in with Universal Login, we have to do a few tasks:

  • Configure Universal Login from the Auth0 Dashboard
  • Create a login method inside our Auth0 Context
  • Use the login method inside our React components

Let's get working!

Configure Universal Login

Since we are using Auth0's SPA SDK, we will be using Auth0's Universal Login.

We will now go back to our Auth0 Dashboard and click on "Universal Login" on the left-hand side.

Once in "Universal Login", if we would like to make changes to colors and the logo, this is where we would do it. Make sure to click SAVE CHANGES at the bottom of the page.

"Auth0's Universal Login is a fast and easy way to add authentication."

Passing a login method to our components

The Auth0 SPA SDK has a loginWithRedirect() method. We can pass this directly to our child components by passing it into value.

We will be adding auth0Client and loginWithRedirect() to our render:

// src/contexts/auth0-context.js

export class Auth0Provider extends Component {

  // ...

  render() {
    const { auth0Client, isLoading, isAuthenticated, user } = this.state; // <-- updated
    const { children } = this.props;

    const configObject = {
      isLoading,
      isAuthenticated,
      user,
      loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p) // <-- updated
    };

    return (
      <Auth0Context.Provider value={configObject}>
        {children}
      </Auth0Context.Provider>
    );
  }
}

We have directly passed the auth0Client method for loginWithRedirect() straight to child components. We are also passing any arguments to it using (...p).

While we are at it, let's pass some more of the library methods for use in our applications. Specifically, we are going to pass:

Here is our final auth0-context.js render() method:

// src/contexts/auth0-context.js

export class Auth0Provider extends Component {

  // ...

  render() {
    const { auth0Client, isLoading, isAuthenticated, user } = this.state;
    const { children } = this.props;

    const configObject = {
      isLoading,
      isAuthenticated,
      user,
      loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
      getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
      getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
      logout: (...p) => auth0Client.logout(...p)
    };

    return (
      <Auth0Context.Provider value={configObject}>
        {children}
      </Auth0Context.Provider>
    );
  }
}

Let's go into App.js and create a way for users to use this login method.

Directing a User to Our Login Page

Inside of App.js, we already have the useContext(Auth0Context) in place. It will be quick to use the login method now:

// src/App.js

// ...imports

function App() {
  const auth0 = useContext(Auth0Context);

  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          <h1>Click Below!</h1>
          <button onClick={auth0.loginWithRedirect} className="button is-danger">
            Login
          </button>
        </div>
      </div>
    </div>
  );
}

export default App;

Notice the important part here is that we have added a button for logging in:

// src/App.js

<h1>Click Below!</h1>
<button onClick={auth0.loginWithRedirect} className="button is-danger">
  Login
</button>

Login Button

Clicking the login button, we can see our new login page thanks to Auth0 Universal Login!

You may have to restart the app in your terminal with npm start.

Auth0 Login View

We have access to Sign Up and Log In. We can also use social logins!

Adding More Social Logins

By default, we have Google authentication enabled. To add Facebook, Twitter, and more logins, it takes 1-click!

Go into our Auth0 Dashboard, click on Connections, and then click on Social. We will enable the authentication systems we want to add. We can click on Facebook and Twitter. We will see that Google is already clicked on for us.

When we add these, Auth0 provides sample credentials for each Facebook and Twitter. When we move to production, we will need to visit Facebook Developers, Twitter Developers, and Google Console to get our own credentials for each. There is a handy warning on the top right of the login page to let us know that we are still using the Auth0 developer keys. Make sure to grab our own credentials when we launch.

Developer Keys view

Now when we go back to our application and click the login button again, we can see the login page with new social options!

The next step is to handle authentication after our user provides either email or social credentials. Auth0 will redirect a user back to our application and we can handle that in our Auth0Provider code.

Handling the Authentication Callback

After we use our authentication page, Auth0 will redirect the user back to our application. Let's authenticate with Twitter. Notice that now there is a code in the URL params.

Code in URL params

We will need to take this and use it to authenticate our user. We'll rearrange our Auth0 context so that we can check for the string code= in the URL. Let's replace our original initializeAuth0 method with the following:

// src/contexts/auth0-context.js

// ...imports

// create a provider
export class Auth0Provider extends Component {

  // ...state, config, compoenentDidMount()

  // initialize the auth0 library
  initializeAuth0 = async () => {
    const auth0Client = await createAuth0Client(this.config);
    this.setState({ auth0Client });

    // check to see if they have been redirected after login
    if (window.location.search.includes('code=')) {
      return this.handleRedirectCallback();
    }

    const isAuthenticated = await auth0Client.isAuthenticated();
    const user = isAuthenticated ? await auth0Client.getUser() : null;
    this.setState({ isLoading: false, isAuthenticated, user });
  };

  // ...render
}

And then we can create a handleRedirectCallback method.

// src/contexts/auth0-context.js

// ...imports

// create a provider
export class Auth0Provider extends Component {

  // ...state, config, componentDidMount(), initializeAuth0()

  // handle the authentication callback
  handleRedirectCallback = async () => {
    this.setState({ isLoading: true });

    await this.state.auth0Client.handleRedirectCallback();
    const user = await this.state.auth0Client.getUser();

    this.setState({ user, isAuthenticated: true, isLoading: false });
  };

  // ...render
}

We are checking for code= in the URL. If that does exist, then we are going to go straight to the handleRedirectCallback() method. This will call Auth0's handleRedirectCallback() method and then go grab the user's information. We will setState() and React will pass all this information down to our application!

At the bottom of this method, we need to update the URL to remove the code=. This code can only be used once, so we need to remove it from the URL to prevent handleRedirectCallback() from running again in the case that the user refreshes the page. We'll use window.history.replaceState() to remove the code.

We will update handleRedirectCallback to look like this:

// src/contexts/auth0-context.js

// ...rest of the file

  // handle the authentication callback
   handleRedirectCallback = async () => {
        this.setState({ isLoading: true });

        await this.state.auth0Client.handleRedirectCallback();
        const user = await this.state.auth0Client.getUser();

        this.setState({ user, isAuthenticated: true, isLoading: false });
        window.history.replaceState({}, document.title, window.location.pathname);
    };

// ...render

Restart the server with npm start. Once up and running again, we can click login, log in on our Universal Login page, and get redirected back to our app. Nothing will have changed in the UI, but if we have React Dev Tools installed, we can see that we have a user in our app!

User Object

Next, let's grab the user out of our Auth0 Context and display it in our app. We'll update App.js to show or hide the login button.

Showing the login button if there is no user

Let's write some code to only show the login button if the app is not loading and if there is no user.

// src/App.js

import React, { useContext } from 'react';
import 'bulma/css/bulma.css';
import { Auth0Context } from './contexts/auth0-context';

function App() {
  const { isLoading, user, loginWithRedirect } = useContext(Auth0Context);

  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          {!isLoading && !user && (
            <>
              <h1>Click Below!</h1>
              <button onClick={loginWithRedirect} className="button is-danger">
                Login
              </button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

We are pulling isLoading and the user directly out of the Auth0 Context. We could use isAuthenticated also.

We are using React Fragments to wrap our code here since React always wants one parent element. The <> </> syntax is a React Fragment short syntax introduced recently.

Showing user information if they are logged in

Let's also write some code to show the user's name and avatar if the app is not loading and there is a user.

By using object destructuring, let's get our user's information to show on the screen!

// src/App.js

// ...imports

function App() {
  const { isLoading, user, loginWithRedirect } = useContext(Auth0Context);

  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          {!isLoading && !user && (
            <>
              <h1>Click Below!</h1>
              <button onClick={loginWithRedirect} className="button is-danger">
                Login
              </button>
            </>
          )}
          {/* this is the new section */}
          {!isLoading && user && (
            <>
              <h1>You are logged in!</h1>
              <p>Hello {user.name}</p>

              {user.picture && <img src={user.picture} alt="My Avatar" />}
            </>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

Now when we are logged in we will see our user's info! We are showing their avatar using user.picture.

Our app has authentication functioning now! We have been able to:

  • Show a login button
  • Log in a user with social authentication
  • Display user information

Seeing the user in Auth0 dashboard

Once we log in with a user, we will be able to see them in our Auth0 dashboard. We can:

  • See how many times they logged in
  • See what service they logged in with
  • See when a user logged in
  • Update their user information

To view this information, return to the Auth0 Dashboard and click on "Users & Roles" located in the left-hand menu. In the drop-down, we will then click on "Users".

Dashboard of Users

Logout For Our React App

Next up let's handle logging out!

We have users logged in. We'll give them a way to logout now. We already have the functionality to log a user out thanks to the SPA SDK's logout() method.

We have also already passed it to our application through React Context. Within the configObject, we are going to add a couple of things:

// src/contexts/auth0-context.js

const configObject = {
  isLoading,
  isAuthenticated,
  user,
  loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
  getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
  getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
  logout: (...p) => auth0Client.logout(...p)
};

return (
  <Auth0Context.Provider value={configObject}>
    {children}
  </Auth0Context.Provider>
);

Let's use that log out inside of our App.js. We also want to be sure to only show the logout button if a user is logged in. Logged out users don't need to see the logout button.

Let's add a "Logout" button:

// src/App.js

// ...imports

function App() {
  const { isLoading, user, loginWithRedirect, logout } = useContext(
    Auth0Context
  );

  return (
    <div className="hero is-info is-fullheight">
      <div className="hero-body">
        <div className="container has-text-centered">
          {!isLoading && !user && (
            <>
              <h1>Click Below!</h1>
              <button onClick={loginWithRedirect} className="button is-danger">
                Login
              </button>
            </>
          )}
          {!isLoading && user && (
            <>
              <h1>You are logged in!</h1>
              <p>Hello {user.name}</p>

              {user.picture && <img src={user.picture} alt="My Avatar" />}
              <hr />

              <button
                onClick={() => logout({ returnTo: window.location.origin })}
                className="button is-small is-dark"
              >
                Logout
          </button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

When using the logout method, we need to provide a place for Auth0 to return our users. We do that with the returnTo property. The flow for logging out is similar to logging in.

  • Redirect users to Auth0
  • Auth0 sends the user back to our app

Important Note: This is where adding our application's URL (http://localhost:3000) to the Allowed Logout URLs of our app settings comes into play.

Now we can click on our logout button! We should be redirected to our application and see the login button.

Logging in and logging out

We have all the things needed for login and logout with Auth0 SPA SDK done! Let's see how the flow of our app looks now:

Renaming Our Auth0 Context to useAuth0()

Our Auth0 Context file is the main file that we need to implement our entire Auth0 SPA SDK strategy.

We can tweak this one more time to make it a bit more readable. To make this change, we will update auth0-context.js:

// src/contexts/auth0-context.js

import React, { Component, createContext, useContext } from 'react'; // <-- updated
import createAuth0Client from '@auth0/auth0-spa-js';

// create the context
export const Auth0Context = createContext();

// export the context as useAuth0
export const useAuth0 = () => useContext(Auth0Context); // <-- new

// ...export class Auth0Provider extends Component {...

We have added useContext and then used it directly in this file. Now we don't have to useContext() in our other components.

Using our new useAuth0()

Now we can update App.js

// src/App.js

import React from 'react';
import 'bulma/css/bulma.css';
import { useAuth0 } from './contexts/auth0-context';

function App() {
  const { isLoading, user, loginWithRedirect, logout } = useAuth0();

  // ...return 

Our code is that much nicer and we have to write less in our React components.

We have everything we need to work with authentication in our React applications. Let's take a look at three more common tasks that we'll need next:

  • Getting user information in other components
  • Grabbing an auth token to use when calling APIs
  • Protecting specific React routes (ie /dashboard page)

Getting User Information in React Components

Let's add a <Header /> component and see how we can use the user information there. The technique is similar to what we did in App.js. Create a new folder in src/ and we will name it components. Within the components folder create a new file: src/components/Header.js:

Creating a Header component

We're going to drop in a lot of code here. The main things to note are that we are using useAuth() just like we did in App.js. We are also using Bulma classes for creating a navbar.

// src/components/Header.js

import React from 'react';
import { useAuth0 } from '../contexts/auth0-context';
import './Header.css'

export default function Header() {
  const { isLoading, user, loginWithRedirect, logout } = useAuth0();

  return (
    <header>
      <nav className="navbar is-dark">
        <div className="container">
          <div className="navbar-menu is-active">
            {/* logo */}
            <div className="navbar-brand">
              <button className="navbar-item">My Cool App!</button>
            </div>

            {/* menu items */}
            <div className="navbar-end">
              {/* if there is no user. show the login button */}
              {!isLoading && !user && (
                <button onClick={loginWithRedirect} className="navbar-item">
                  Login
                </button>
              )}

              {/* if there is a user. show user name and logout button */}
              {!isLoading && user && (
                <>
                  <button className="navbar-item">{user.name}</button>
                  <button
                    onClick={() => logout({ returnTo: window.location.origin })}
                    className="navbar-item"
                  >
                    Logout
                  </button>
                </>
              )}
            </div>
          </div>
        </div>
      </nav>
    </header>
  );
}

We will then style those buttons with our own CSS. Create a file within the components folder named, Header.css.

// src/components/Header.css

button {
    background: transparent;
    outline: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
}

Now before this can work, let's import this file into our App.js file.

Adding the Header to our main App

Let's add the Header.js component to our App.js:

// src/App.js

import React from 'react';
import 'bulma/css/bulma.css';
import { useAuth0 } from './contexts/auth0-context';
import Header from './components/Header';

function App() {
  const { isLoading, user, loginWithRedirect, logout } = useAuth0();

  return (
    <>
      <Header />

      <div className="hero is-info is-fullheight">
        <div className="hero-body">
          <div className="container has-text-centered">
            {!isLoading && !user && (
              <>
                <h1>Click Below!</h1>
                <button onClick={loginWithRedirect} className="button is-danger">
                  Login
              </button>
              </>
            )}
            {!isLoading && user && (
              <>
                <h1>You are logged in!</h1>
                <p>Hello {user.name}</p>

                {user.picture && <img src={user.picture} alt="My Avatar" />}
                <hr />

                <button
                  onClick={() => logout({ returnTo: window.location.origin })}
                  className="button is-small is-dark"
                >
                  Logout
                </button>
              </>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

export default App;

Notice that we have to wrap our entire template in the React Fragment shorthand <> </>.

With that new header in, we should now see this in our browser:

Entire view of app

We could have passed all the auth information down to our Header.js component as props, but this example is to illustrate that we can grab authentication information anywhere in our app using useAuth0().

Try logging in and out from any of the login/logout buttons now. Everything will update accordingly and our auth state is shown in multiple components!

Conclusion

In this tutorial, we've seen a lot of great features of the Auth0 SPA SDK and been able to integrate it into a React application.

We have been able to:

  • Use Auth0's Universal Login
  • Authenticate a user using social and email
  • Get user information in our app
  • Get a token to make authenticated API calls

I hope this was helpful. Feel free to drop in any of this code into your own applications, specifically the auth0-context.js file. This is the core of adding Auth0 authentication to your React apps!

About Auth0

Auth0, the identity platform for application builders, provides thousands of enterprise customers with a Universal Identity Platform for their web, mobile, IoT, and internal applications. Its extensible platform seamlessly authenticates and secures more than 2.5B logins per month, making it loved by developers and trusted by global enterprises. The company's U.S. headquarters in Bellevue, WA, and additional offices in Buenos Aires, London, Tokyo, Sydney, and Singapore, support its customers that are located in 70+ countries.

For more information, visit https://auth0.com or follow @auth0 on Twitter.