This tutorial shows how to use PlusAuth with React Single Page Application. If you do not have a PlusAuth account, register from here .
This tutorial follows plusauth-react-starter sample project on Github. You can download and follow the tutorial via the sample project.
After you sign up or log in to PlusAuth, you need to create a client to get the necessary configuration keys in the dashboard. Go to Clients and create a client with the type of Single Page Application
You will need your Client Id
for interacting with PlusAuth. You can retrieve it from the created client's details.
When PlusAuth authenticates a user, it needs a URI to redirect back with access and id token. That URI must be in your client's Redirect URI
list. If your application uses a redirect URI which is not white-listed in your PlusAuth Client, you will receive an error.
The same thing applies to the logout URIs. After the user logs out, you need a URI to be redirected.
If you are following the sample project, the Redirect URL you need to add to the Redirect URIs fields are http://localhost:3000/callback
and http://localhost:3000/silent-renew.html
. The Logout URL you need to add to the Post Logout Redirect URIs field is http://localhost:3000/
.
Create new React project using npx
and create-react-app
. Add a router to the project to render different views.
# Create the application using the create-react-app.
npx create-react-app plusauth-react-starter
# Move into the project directory
cd plusauth-react-starter
# Add the router, as we will be using it later
npm install react-router-dom@6
# Add bootstrap for styling
For interacting with PlusAuth it is advised to use an OpenID Connect library. In this tutorial we will be using oidc-client-js but you could use any OpenID Connect library.
Install oidc-client-js
with the following command
npm install @plusauth/oidc-client-js
oidc-client-js
is an OpenID Connect (OIDC) and OAuth2 library for browser based JavaScript applications. You can find source code on Github and the API documentation here .
We will be using dotenv
files for maintaining providing some constant values.
Create the .env
file at the root of your project with the following and modify values accordingly.
REACT_APP_OIDC_ISSUER =https://<YOUR_PLUSAUTH_TENANT_NAME>.plusauth.com/
REACT_APP_CLIENT_ID =<YOUR_PLUSAUTH_CLIENT_ID>
If you are following the sample project, rename .env.example
to .env
and replace the values accordingly.
Let's start by editing our application's entry point file. Edit index.js
in src
folder. Import auth
file that we will create later.
import React from 'react'
import ReactDOM from 'react-dom'
import Auth from './auth'
import 'bootstrap/dist/css/bootstrap.min.css'
// Make auth object global to access from anywhere
document. getElementById ( 'root' )
We need to initialize our OIDC Client library to handle authentication-related operations. Create auth.js
in src
folder. Configure oidc-client-js
as following:
import { OIDCClient } from '@plusauth/oidc-client-js'
const Auth = new OIDCClient ({
issuer: process.env. REACT_APP_OIDC_ISSUER ,
client_id: process.env. REACT_APP_CLIENT_ID ,
redirect_uri: 'http://localhost:3000/callback' ,
response_mode: 'form_post' ,
response_type: 'id_token token' ,
post_logout_redirect_uri: 'http://localhost:3000/' ,
silent_redirect_uri: 'http://localhost:3000/silent-renew.html'
You may have noticed that the values defined in the Configure Client section are used here. If you have used different values make sure to update this file accordingly.
Now let's define our application's router. We are going to define the routes of our views.
Create route.js
in src/router
folder as following:
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import PrivateRoute from './privateRoute'
import SilentRenew from '../components/silentRenew'
import AuthCallback from '../components/authCallback'
import Home from '../views/home'
import Profile from '../views/profile'
import Unauthorized from '../views/unauthorized'
export const RouteList = (
< Route path = "/" element = {< Home />} />
< Route path = "/callback" element = {< AuthCallback />} />
< Route path = "/silent-renew.html" element = {< SilentRenew />} />
< Route path = "/unauthorized" element = {< Unauthorized />} />
PrivateRoute
component will ensure the child components are accessible only by authenticated users. Now create privateRoute.js
in src/router
as following:
import React from 'react'
import { Navigate } from 'react-router-dom'
export default class PrivateRoute extends React . Component {
// auth.isLoggedIn returns promise
// Get isLoggedIn value using await and use in render
async componentDidMount () {
const isLoggedIn = await window.$auth. isLoggedIn ( true )
this . setState ({ isLoggedIn: isLoggedIn, isLoading: false })
if ( this .state.isLoading) return < div >Loading...</ div >
else if ( this .state.isLoggedIn) return this .props.children
// If logged in then go to protected route
else return < Navigate to = "/unauthorized" /> // else navigate to unauthorized page
Until now, we have defined our authentication helper and routes. It is time to create the pages and interact with auth helper.
Let's create a simple layout for our application. Add Header
component and BrowserRouter
to App.js
import Header from './components/header'
import 'bootstrap/dist/css/bootstrap.min.css'
import { BrowserRouter } from 'react-router-dom'
import { RouteList } from './router/route'
< BrowserRouter basename = { '/' }>
Create header.jsx
under src/components
folder. It will be a basic header. If a user is authenticated, it will show the user's identifier and a Logout
button. If not, a Login
button will be there to initiate login.
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
export default class Header extends Component {
this .state = { user: null }
async componentDidMount () {
window.$auth. on ( 'user_login' , ({ user }) => this . setState ({ user: user }))
window.$auth. on ( 'user_logout' , () => this . setState ({ user: null }))
const user = await window.$auth. getUser ()
this . setState ({ user: user })
if ( ! this .state.user.given_name || ! this .state.user.family_name) {
return this .state.user.username || this .state.user.email
return `${ this . state . user . given_name } ${ this . state . user . family_name }`
< nav className = "navbar navbar-expand-md navbar-dark bg-dark fixed-top" >
< a className = "navbar-brand container-fluid" href = "/" >
< div className = "collapse navbar-collapse" id = "navbarsExampleDefault" >
< ul className = "navbar-nav" ></ ul >
< li className = "nav-item navbar-nav text-light" >
< Link className = "nav-link" to = "/profile" >
Logged in as: { this . userDisplayName ()}
onClick = {() => window.$auth. logout ()}
< li className = "nav-item navbar-nav" >
onClick = {() => window.$auth. login ()}
To handle authorization results after a successful login, we need a simple page and let the library handle the authentication result. Create authCallback.jsx
under src/components
folder.
import React from 'react'
import { useNavigate } from 'react-router-dom'
class AuthCallback extends React . Component {
async componentDidMount () {
await window.$auth. loginCallback ()
// useNavigate cannot be used in Class Components
// function component created and exported to use useNavigate()
function WithNavigate ( props ) {
let navigate = useNavigate ()
return < AuthCallback { ... props} navigate = {navigate} />
export default WithNavigate
Access tokens retrieved from PlusAuth have a life span. oidc-client-js
automatically provides access_token
renewal without too much hassle. Before your access token expires, it will receive a new one in the background so that your users will have a flawless app experience without signing in again.
Create silentRenew.jsx
under src/components
folder as following:
import React from 'react'
import { OIDCClient } from '@plusauth/oidc-client-js'
export default class SilentRenew extends React . Component {
async componentDidMount () {
issuer: process.env. REACT_APP_OIDC_ISSUER ,
Create home.jsx
under src/views
.
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
export default class Home extends Component {
this .state = { user: null }
async componentDidMount () {
window.$auth. on ( 'user_login' , ({ user }) => this . setState ({ user: user }))
window.$auth. on ( 'user_logout' , () => this . setState ({ user: null }))
const user = await window.$auth. getUser ()
this . setState ({ user: user })
< div className = "jumbotron" >
< div className = "container" >
< h1 className = "display-3" >
Hello, { this .state.user ? this .state.user.username : 'World' }!
This is a template for a simple login/register system. It includes
the OpenID Connect Implicit Flow. To view Profile page please login.
< Link className = "btn btn-primary btn-lg" to = "/profile" >
className = "btn btn-primary btn-lg"
onClick = {() => window.$auth. login ()}
Create profile.jsx
under src/views
.
import React, { Component } from 'react'
export default class Profile extends Component {
this .state = { user: null }
async componentDidMount () {
window.$auth. on ( 'user_login' , ({ user }) =>
this . setState ({user: user})
window.$auth. on ( 'user_logout' , () => ( this . setState ({user: null })))
const user = await window.$auth. getUser ()
this . setState ({user: user})
< div className = "container" >
<>< h3 >Welcome { this .state.user.username} !</ h3 >< pre >User object: { JSON . stringify ( this .state.user, null , 2 )} </ pre ></>
We will display a page whenever a user tries to access a protected route without signing in.
Create unauthorized.jsx
under src/views
.
export default function Unauthorized () {
< div className = "container" >
< p >You must log in to view the page</ p >
< button className = "btn btn-primary" onClick = {() => window.$auth. login ()}>Log in</ button >
That's it. Start your app and point your browser to http://localhost:3000 . Follow the Log In link to log in or sign up to your PlusAuth tenant. Upon successful login or signup, you should be redirected back to the application.