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
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
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.
Create a new single page application and give it a name.
After you have created your application,take note of your Domain and client Id as we shall need them later.
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);
});
};
}
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')
);
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;
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>
)
}
}
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;
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;
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>
);
}
}
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;
}
And that's it. if your successful, you should have something looking like the one below.
Top comments (0)