Adding Authentication to a Web Application with Auth0, React, and JWT
Don't be left in the dark trying to set up an authentication layer. Learn how to use React and Auth0 to enable authenticated-only sections within a web application, as well as to retrieve protected resources, and more!
Join the DZone community and get the full member experience.
Join For FreeSetting up an authentication layer is, without doubt, one of the most challenging yet necessary tasks within any web application. Not only does the application in question always needs to ensure the most basic functionality is set up by default (such as login, logout, reset password), but additionally, it’s required to develop all the libraries to handle the validation of the credentials, the connections to the database responsible for the user data, session management, and general security.
Enter Auth0. Auth0 is an online web service that handles authentication protocols like OAuth2, LDAP, and OpenID Connect to allow clients to create authenticated services without need to build the entire infrastructure. In particular, Auth0 uses the standard RFC 7519 approved by the IETF, better known as JSON Web Tokens (JWT), to communicate with its clients that an authentication flow has been performed. This way, an application can retrieve user related data and showcase protected information to only logged in users.
In the following article we will learn how to use React and Auth0 to enable authenticated-only sections within a web application, as well as to retrieve protected resources. The application will show the logged-in user their GitHub repositories, so we will use our application with Auth0 and OAuth’s GitHub integration. Additionally, we will leverage the auth0-lock JavaScript library version 10 for performing calls against Auth0, and use the create-react-app library from Facebook to bootstrap our application. Knowledge aboutNode.js and npm will be assumed.
Links
- Auth0.com
- Free account required for performing OAuth2 with GitHub.
- Create React App
- Required for setting up our sample application.
- Download.Repos.Club
- Repository with final code.
Setup
The initial setup is fairly easy thanks to Facebook’s create-react-app utility. Calling the tool with a name will set up a repository with a development workflow toolchain that includes Webpack, Babel, Hot-Reload, and ES6 support.
\$ create-react-app download.repos.club
After running the command successfully, the folder will have the following structure:
── README.md
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
└── logo.svg
We will focus on the src folder for our application. As with many React applications, we want to make sure our view is in place before we perform any business logic on the application. The key view components for our application will be the Login/Logout button, and the Repositories List View. The files src/Navbar.js and src/Repo.js showcase each view:
src/Navbar.js
import React from ‘react’
export const Navbar = ({loggedIn, onClick}) => {
return (
<nav className=’pt-navbar pt-dark’>
<div className=’pt-navbar-group pt-align-left’>
<div className=’pt-navbar-heading’>Download Repos
</div>
</div>
<div className=’pt-navbar-group pt-align-right’>
<button onClick={onClick()} className=’pt-button pt-minimal pt-icon-user’>
{
loggedIn
? ‘Logout’
: ‘Log in’
}
</button>
</div>
</nav>
)
}
src/Repo.js
import React from 'react'
import emoji from 'node-emoji'
import {
Colors,
AnchorButton
}
from '@blueprintjs/core'
export const Repo = ({name, description, stars, forks, updatedAt, forked, disabled, downloadRepo}) => (
<div className='Card pt-card pt-elevation-3'>
<div className='Repo'>
<div className='Repo__container'>
<div className='Repo__headline-container'>
<span className='Repo__name'>
<span className={'pt-icon-standard ${forked ? ‘pt-icon-git-branch’ : ‘pt-icon-git-repo’} Repo__icon'} />
<b>{name}</b>
{ forked && <span className=’pt-tag pt-intent-warning Repo__tag’>Fork</span> }
</span>
<span style={{color: Colors.GRAY1}}className='Repo__description'>{description ? emoji.emojify(description) : ''}</span>
</div>
<div className='Repo__metadata-container'>
<span className='Repo__stars'><b>{stars}</b> stars</span>
<span className='Repo__forks'><b>{forks}</b> forks</span>
</div>
</div>
<div className='Repo__container'>
<span className='Repo__latest-update'>Last Updated:
<b>{new Date(updatedAt).toDateString()}</b></span>
</div>
</div>
<div className='Repo__container'>
<span className='Repo__latest-update'>Last Updated: <b>{new Date(updatedAt).toDateString()}</b></span>
<div className='Repo__actions-container pt-button-group’>
<AnchorButton href={downloadRepo} download={'${name}.zip'} iconName='download'> Download </AnchorButton>
{/*<Button className='pt-intent-danger' iconName='delete' disabled={disabled}> Delete </Button>*/}
</div>
</div>
</div>
</div>
)
Note: We are using Palantir’s Blueprint React components module to display some of the objects in our application. Visit blueprintjs.com to learn more about BlueprintJS.
As we can see, both our view components receive a series of parameters, particularly the
src/Repo.js, that contains authenticated information. We need to use Auth0’s library Lock to help us retrieve that information from our user. Including auth0-lock in our library and adding an ES6 class wrapper around it ensures we can request the user GitHub credentials. The class, which will be located in src/Auth.js, will look something like this:
src/Auth.js
import Auth0Lock from ‘auth0-lock’
export default class Auth {
constructor (clientId, domain, callback) {
// Configure Auth0
this.lock = new Auth0Lock(clientId, domain, { redirect: true, allowSignUp: false })
// Add callback for lock `authenticated` event
this.lock.on(‘authenticated’, this._doAuthentication.bind(this, callback))
// binds login functions to keep this context
this.login = this.login.bind(this)
}
_doAuthentication (callback, authResult) {
this.setToken(authResult.idToken)
callback()
}
login () {
// Call the show method to display the widget.
this.lock.show() } loggedIn () {
// Checks if there is a saved token and it’s still valid
return !!this.getToken()
}
setToken (idToken) {
// Saves user token to localStorage
localStorage.setItem(‘id_token’, idToken)
}
getToken () {
// Retrieves the user token from localStorage
return localStorage.getItem(‘id_token’)
}
logout () {
// Clear user token and profile data from localStorage
localStorage.removeItem(‘id_token’)
}
}
The most important parts of this class are the constructor and login method. The constructor initializes the Auth0 library with your own credentials, while the login will trigger the Auth0 modal display for showing the user credentials. Since we are only interested in our user’s GitHub information, we don’t want to allow signing up through Auth0. This can be specified as a parameter to Lock. You can read more about Lock in auth0.com/docs/libraries/lock.
Before we continue, we need to retrieve our Client ID and Domain from Auth0. Additionally, ensure that localhost is included in the list of “Allowed Callback URLs” and “Allowed Origins (CORS)” within the Auth0 dashboard. To be able to use GitHub as a Connection, you need to create a new application under Settings > OAuth Applications in GitHub, and pass both Client ID and Client Secret from the new registered application. Add Auth0’s callback URL within the GitHub application, which should be in the form https://<user>.auth0.com/login/callback.
If everything has been done correctly, we should be able to wire our application with the Auth class with the button, and display the Auth0 modal inside.
We are now able to log in our user, but we still need to fetch its data. That’s where JSON Web Tokens (JWT) come in. On a successful login, src/Auth.js will store the JWT of the authenticated request as an id_token inside our local storage. We can then use this token to request Auth0’s profile information of the user. Since JWTs are encoded with a secret that only Auth0 knows, they are safe to send and receive in a client-side application. We will retrieve the JWT through an API class, and then perform the required queries to both Auth0 and GitHub. Our now src/Api.js looks like this:
import axios from ‘axios’
import Auth from ‘./Auth’
export default class Api {
constructor (callback) {
this.auth = new Auth(‘xRTGXVGR03uOlQMRds6ZpU0fx8OjLakE’,
‘jjperezaguinaga.auth0.com’, callback)
}
getRepos () {
return this.isLoggedIn()
? this.getProfile()
: Promise.reject(new Error(‘User is not authenticated’))
}
async getProfile () {
const profile = await axios.post(‘https://jjperezaguinaga.auth0.com/tokeninfo’, {id_token: this.auth.getToken()})
return Promise.all([
profile.data,
axios.get(`https://api.github.com/users/${profile.data.nickname}/repos?per_page=100&sort=updated`)
])
}
isLoggedIn () {
return this.auth.loggedIn()
}
login () {
this.auth.login()
} logout () {
this.auth.logout()
}
}
We are taking advantage of both Promise and Axios to perform asynchronous requests to our endpoints, and resolve them gracefully, respectively. Additionally, we use await and async to control the flow of our requests, and chain each response to then retrieve the user GitHub repositories. Finally, src/Api.js provides us with utilities to communicate with our src/Auth.js class and log out whenever we don’t need the resources anymore.
Conclusion
The process we used to authenticate our user is known as the Implicit Grant flow defined by RFC 6749, better known as OAuth2. Due to the security restrictions of any client-side application, one can perform limited operations against the Resource Server. In order to use Auth0 to perform calls against an API (such as creating a new repository in GitHub, for instance), it’s required to set up a server that is able to perform the access_token handshake with the Resource Server. However, even without a server, we are able to retrieve basic information about our user, such as their profile and repositories. A few years ago it would have been required to have a full-blown dedicated server and database to perform such tasks.
Although we picked Auth0 to showcase this flow, it’s important to mention that Auth0 is not the only service in the authentication-as-a-service industry. Amazon Cognito and Stormpath provide similar solutions, both with their pros and cons. Particularly, Amazon Cognito interacts perfectly with the AWS ecosystem, and might be a better option over Auth0 if the consumers of your application are working with AWS-related resources.
More Web Dev Goodness
If you'd like to see other articles in this guide, be sure to check out:
Opinions expressed by DZone contributors are their own.
Trending
-
How To Use the Node Docker Official Image
-
Integration Architecture Guiding Principles, A Reference
-
MLOps: Definition, Importance, and Implementation
-
DevOps Pipeline and Its Essential Tools
Comments