DEV Community

Eric Kalujja
Eric Kalujja

Posted on

Auth0 authentication with react

cover photo

Photo by chris panas on Unsplash

Authentication is a major part of most Apps. In most cases, you will need some kind of authentication in your next project. Wouldn't it be nice if you got this out of the way once and for all? well... let's just get to it.

We are going to use Auth0 , an identity management platform for application builders to build an app that allows users to login/signup with their Gmail account and display a user profile with their information. You can add other social login options as Auth0 supports quite a number.

Note: I developed this taking Cory house's Securing React Apps with Auth0 course on pluralsight

I assume you know the basics of React.

You can find the project here on Github.

Step 1.
create a new react app.

npx create-react-app auth0-with-react
Enter fullscreen mode Exit fullscreen mode

Next we shall install all npm packages we shall need.
In the auth0-with-react folder, run

npm install auth0-js dotenv react-router-dom
Enter fullscreen mode Exit fullscreen mode

auth0-js will allow us integrate Auth0 authentication in our react app.

react-router-dom will be used for dynamic navigation between pages in our App.

dotenv is for reading the .env file where we shall be storing our Auth0 credentials.

At this point, head over to Auth0 and signup for an account.

Alt Text

Create a new single page application and give it a name.

Alt Text

After you have created your application,take note of your Domain and client Id as we shall need them later.

Alt Text

In your src/ directory, create Auth.js file. This is the file where our authentication stuff will go.
Add the following code in the Auth.js

Auth.js

import auth0 from "auth0-js";
import dotenv from "dotenv";

//read .env file
dotenv.config();

export default class Auth {
  // pass history for redirection after login
  constructor(history) {
    this.history = history;
    this.userProfile = null;

    // create a new auth object with your auth0 credentials (domain, clientID, redirectUri)
    // You will have to setup a redirectUri in your Auth0 app's settings. in this case its http://localhost:3000/callback
    this.auth0 = new auth0.WebAuth({
      domain: process.env.REACT_APP_AUTH0_DOMAIN,
      clientID: process.env.REACT_APP_AUTH0_CLIENTID,
      redirectUri: process.env.REACT_APP_AUTH0_CALLBACK_URL,
      responseType: "token id_token", // we want a token and id_token returned in the response
      scope: "openid profile email", 
      // openid is auth protocol we are using. 
      // we want access to the profile info and email from Gmail in our case
    });
  }

  // login method
  login = () => {
    this.auth0.authorize(); // this is all you need to login
  };

  // Extract the returned tokens and store in local storage
  handleAuthentication = () => {
    // Parse the url hash and extract the returned tokens depending on the transaction.
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult);
        this.history.push("/");
      } else if (err) {
        this.history.push("/");
        alert(`Error: ${err.error}. check console`);
        console.log(err);
      }
    });
  };

  setSession = (authResult) => {
    //set the time that the access token will expire
    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);
  };

  //check if user is authentecated
  isAuthenticated = () => {
    const expiresAt = JSON.parse(localStorage.getItem("expires_at"));
    return new Date().getTime() < expiresAt;
  };

  logout = () => {
    // clear localstorage
    localStorage.removeItem("access_token");
    localStorage.removeItem("id_token");
    localStorage.removeItem("expires_at");
    this.userProfile = null;
    //logout from server and redirect to home page
    this.auth0.logout({
      clientID: process.env.REACT_APP_AUTH0_CLIENTID,
      returnTo: "http://localhost:3000/",
    });
  };

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

  // Get user's profile
  getProfile = (cb) => {
    if (this.userProfile) return cb(this.userProfile);
    this.auth0.client.userInfo(this.getAccessToken(), (err, profile) => {
      if (profile) this.userProfile = profile;
      cb(profile, err);
    });
  };
}

Enter fullscreen mode Exit fullscreen mode

Let's now pull in react-router-dom to handle dynamic routing in our app.
In your index.js, lets add it like below.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import{ BrowserRouter as Router, Route} from 'react-router-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <Router>
    <Route component={App} />
  </Router>,
  document.getElementById('root')
);

Enter fullscreen mode Exit fullscreen mode

Now in our App.js lets pull in our Auth object and use it to protect our /profile route by checking if user is authenticated.

App.js

import React from "react";
import { Route, Redirect } from "react-router-dom";
import Home from "./Home";
import Profile from "./Profile";
import Nav from "./Nav";
import Auth from "./Auth";
import Callback from "./Callback";

function App(props) {
  const auth = new Auth(props.history);
  return (
    <>
      <Nav auth={auth} />
      <div className="body">
        <Route
          path="/"
          exact
          render={(props) => <Home auth={auth} {...props} />}
        />
        <Route
          path="/callback"
          exact
          render={(props) => <Callback auth={auth} {...props} />}
        />
        <Route
          path="/profile"
          exact
          render={(props) =>
            auth.isAuthenticated() ? (
              <Profile auth={auth} {...props} />
            ) : (
              <Redirect to="/" />
            )
          }
        />
      </div>
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

You will notice we are importing Home,Profile,Nav and Callback in the App component, let's go ahead and create these.
starting with the Callback.js, in our /src directory.

Callback.js

import React, { Component } from 'react'

export default class Callback extends Component {
    componentDidMount = () => {
      // handle authentication if expected values are in the URL.
      if(/access_token|id_token|error/.test(this.props.location.hash)){
          this.props.auth.handleAuthentication();
      } else {
          throw new Error("Invalid callback URL");
      }
    }


    render() {
        return (
            <h1>
                Loading...
            </h1>
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Auth0 returns a hash string containing an access token and id token to the callback uri you provide in your Auth0 app dashboard.
In the above code, we are checking if the access token or id token are present in the location object and if so, we call the handleAuthentication() method of the auth object which we passed as a prop from the App.js

Next Home component. still in the src/ directory.

Home.js

import React from "react";
import { Link } from "react-router-dom";

const Home = (props) => {
  return (
    <div>
      <h1>Home</h1>
      {props.auth.isAuthenticated() ? (
        <Link to="/profile">View Profile</Link>
      ) : null}
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

For our navbar, lets create Nav.js in the src/ directory.

Nav.js

import React from "react";
import { Link } from "react-router-dom";

const Nav = (props) => {
  const { isAuthenticated, login, logout } = props.auth;
  return (
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/profile">Profile</Link>
        </li>
        <li>
          <button onClick={isAuthenticated() ? logout : login}>
            {isAuthenticated() ? "log out" : "log in"}
          </button>
        </li>
      </ul>
    </nav>
  );
};
export default Nav;
Enter fullscreen mode Exit fullscreen mode

In the code above, we are linking to our home and profile routes and also displaying the login/logout buttons depending on whether the user is authenticated or not.

And lastly in the Profile component. We fetch the user profile using the getProfile() method of our auth object, store the returned profile object in state and use it to display the user's profile.
Lets create a profile.js file in your src/ directory and add the following code.

Profile.js

import React, { Component } from "react";
import "./profile.css";

export default class Profile extends Component {
  state = {
    profile: null,
    error: "",
  };
  componentDidMount() {
    this.loadUserProfile();
  }

  loadUserProfile() {
    this.props.auth.getProfile((profile, error) => {
      this.setState({ profile, error });
    });
  }
  render() {
    const { profile } = this.state;
    if (!profile) return null;
    return (
      <div className="profile">
        <h1>Profile</h1>
        <img src={profile.picture} alt="profile pic" />
        <div className="list-info">
          <div className="list">
            <span className="property">Name</span>
            <span>{profile.name}</span>
          </div>
          <div className="list">
            <span className="property">Given Name</span>
            <span>{profile.given_name}</span>
          </div>
          <div className="list">
            <span className="property">Family Name</span>
            <span>{profile.family_name}</span>
          </div>
          <div className="list">
            <span className="property">Nick Name</span>
            <span>{profile.nickname}</span>
          </div>
          <div className="list">
            <span className="property">Email</span>
            <span>{profile.email}</span>
          </div>
        </div>

      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

And here is the css for Profile

profile.css


.profile{
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}
img{
    max-width: 100px;
    max-height: 100px;
    border: 0px solid;
    border-radius: 50px;
}
.list-info{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
div.list{
    margin: 1em;
}
div>span{
    margin: 0.2em;
}
.property{
    color: #fff;
    background-color: #f0582a;
    border-radius: 8px;
    padding: 0.2em;
}
Enter fullscreen mode Exit fullscreen mode

And that's it. if your successful, you should have something looking like the one below.
Alt Text

Top comments (0)