SPA
React
In this quickstart you are going to build an application with React and integrate it with FusionAuth. You’ll be building it for ChangeBank, a global leader in converting dollars into coins. It’ll have areas reserved for users who have logged in as well as public facing sections.
The docker compose file and source code for a complete application are available at https://github.com/FusionAuth/fusionauth-quickstart-javascript-react-web.
Prerequisites
- Node v18: This will be used to run the React application.
- Docker: The quickest way to stand up FusionAuth. (There are other ways).
This app has been tested with Node v18 and React v18.2.0. This example should work with other compatible versions of Node and React.
General Architecture
While this sample application doesn’t have login functionality without FusionAuth, a more typical integration will replace an existing login system with FusionAuth.
In that case, the system might look like this before FusionAuth is introduced.
Request flow during login before FusionAuth
The login flow will look like this after FusionAuth is introduced.
Request flow during login after FusionAuth
In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.
Getting Started
In this section, you’ll get FusionAuth up and running and use React CLI to create a new application.
Clone the Code
First off, grab the code from the repository and change into that directory.
git clone https://github.com/FusionAuth/fusionauth-quickstart-javascript-react-web.git
cd fusionauth-quickstart-javascript-react-web
Run FusionAuth via Docker
In the root directory of the repo you’ll find a Docker compose file (docker-compose.yml) and an environment variables configuration file (.env). Assuming you have Docker installed on your machine, you can stand up FusionAuth up on your machine with:
docker compose up -d
This will start three containers, once each for FusionAuth, Postgres, and Elastic.
Here you are using a bootstrapping feature of FusionAuth, called Kickstart. When FusionAuth comes up for the first time, it will look at the kickstart/kickstart.json
file and configure FusionAuth to a certain initial state.
If you ever want to reset the FusionAuth system, delete the volumes created by docker-compose by executing docker compose down -v
, then re-run docker compose up -d
.
FusionAuth will be configured with these settings
- Your client Id is
e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
. - Your client secret is
super-secret-secret-that-should-be-regenerated-for-production
. - Your example username is
richard@example.com
and the password ispassword
. - Your admin username is
admin@example.com
and the password ispassword
. - The base URL of FusionAuth
http://localhost:9011/
.
You can log into the FusionAuth admin UI and look around if you want, but with Docker/Kickstart you don’t need to.
Create a basic React application
Now you are going to create a basic React application using the React CLI. While this section builds a simple React application, you can use the same configuration to integrate your existing React application with FusionAuth.
npx create-react-app changebank && cd changebank
It may ask you to install the create-react-app
package, just confirm it. Further information can be found on the Create React App Website
We are going to use the Hosted Backend feature of FusionAuth, so you don’t need to worry about setting up a backend server.
Install the FusionAuth React SDK, and React Router, which we’ll use to manage the routes in our application:
npm install @fusionauth/react-sdk react-router-dom
Next, you’ll need to configure the SDK with your FusionAuth URL and client ID. You can do this by creating a new file under src/config.js
:
export const config = {
clientID: 'e9fdb985-9173-4e01-9d73-ac2d60d1dc8e',
serverUrl: 'http://localhost:9011',
redirectUri: 'http://localhost:3000',
}
Then replace the contents of src/index.js
with the below to configure the SDK:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
import {FusionAuthProvider} from "@fusionauth/react-sdk";
import {config} from "./config";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<FusionAuthProvider {...config}>
<App/>
</FusionAuthProvider>
</BrowserRouter>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
We also set up the BrowserRouter
from React Router, which will allow us to use the Routes
and Route
component to define the routes later.
Our example application is going to have a home page and an account page. The account page will be protected and only visible to logged in users.
Create a Home Page
The next step is to get a basic home page up and running. We’ll take this opportunity to copy in all the images and CSS style files that you’ll need for the application.
Run the following copy commands to copy these files from the quickstart repo into your project. This assumes that you checked the quickstart repo out into the parent directory. If that’s not the case, replace the ..
below with your actual repo location.
cp -r ../complete-application/src/assets src && \
cp -r ../complete-application/src/index.css src
The home page will be a simple page with a welcome message and a login link. Create a new directory pages
under src
, in that directory create a new file src/pages/HomePage.js
:
import background from '../assets/money.jpg';
import {useNavigate} from "react-router-dom";
import {useFusionAuth} from "@fusionauth/react-sdk";
import {useEffect} from "react";
export default function HomePage() {
const navigate = useNavigate();
const {isAuthenticated, isLoading} = useFusionAuth();
useEffect(() => {
if (isAuthenticated) {
navigate('/account');
}
}, [isAuthenticated, navigate]);
if (isAuthenticated || isLoading) {
return null;
}
return (
<div className="column-container">
<div className="content-container">
<div style={{marginBottom: '100px'}}>
<h1>Welcome to Changebank</h1>
<p>To get started, <a style={{cursor: "pointer"}}>log in or create a new account</a>.</p>
</div>
</div>
<div style={{flex: 0}}>
<img src={background} style={{maxWidth: '800px'}} alt=""/>
</div>
</div>
)
}
Create an Account Page
The account page will be a simple page that displays a random balance for the logged in user. Create a new file src/pages/AccountPage.js
:
import {useEffect, useState} from "react";
import {useNavigate} from "react-router-dom";
import {useFusionAuth} from "@fusionauth/react-sdk";
let dollarUS = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
});
export default function AccountPage() {
const [balance,] = useState(dollarUS.format(Math.ceil(Math.random() * 100000) / 100));
const navigate = useNavigate();
const {isAuthenticated, isLoading} = useFusionAuth();
useEffect(() => {
if (!isAuthenticated) {
navigate('/');
}
}, [isAuthenticated, navigate]);
if (!isAuthenticated || isLoading) {
return null;
}
return (
<div className="column-container">
<div className="app-container">
<h3>Your balance</h3>
<div className="balance">{balance}</div>
</div>
</div>
)
}
Create A Make Change Page
The make change page will be a simple page that displays an input field for the user to enter a dollar amount and a button to convert that amount into coins. Create a new file src/pages/MakeChangePage.js
:
import {useFusionAuth} from "@fusionauth/react-sdk";
import {useEffect, useState} from "react";
import {useNavigate} from "react-router-dom";
let dollarUS = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
});
export default function MakeChangePage() {
const [amount, setAmount] = useState(0);
const [change, setChange] = useState(null);
const navigate = useNavigate();
const {isAuthenticated, isLoading} = useFusionAuth();
useEffect(() => {
if (!isAuthenticated) {
navigate('/');
}
}, [isAuthenticated, navigate]);
const makeChange = (e) => {
e.stopPropagation();
e.preventDefault();
const total = amount;
const nickels = Math.floor(amount / 0.05);
const pennies = Math.round((amount - nickels * 0.05) * 100);
setChange({total, nickels, pennies})
};
if (!isAuthenticated || isLoading) {
return null;
}
return (
<div className="app-container change-container">
<h3>We Make Change</h3>
{change && (
<div className="change-message">
We can make change for {dollarUS.format(change.total)} with {change.nickels} nickels and {change.pennies} pennies!
</div>
)}
<form onSubmit={makeChange}>
<div className="h-row">
<div className="change-label">Amount in USD: $</div>
<input className="change-input" name="amount" value={amount} onChange={e => setAmount(e.target.value)} type="number" step=".01"/>
<input className="change-submit" type="submit" value="Make Change"/>
</div>
</form>
</div>
);
}
Authentication
You now have created a basic React application with a home page and an account page.
Depending on the user’s authentication state, the login or logout button should be displayed in the header. For this create a new directory components
in src
and a new file src/components/LogoHeader.js
:
import {useFusionAuth} from "@fusionauth/react-sdk";
import logo from "../assets/changebank.svg";
export default function LogoHeader() {
const {isAuthenticated, user, login, logout} = useFusionAuth();
return (
<div id="logo-header">
<img src={logo} alt="Change Bank" width="257" height="55"/>
{
isAuthenticated ? (
<div className="h-row">
<p className="header-email">
{user.email}
</p>
<a className="button-lg" style={{cursor: "pointer"}} onClick={() => logout()}>
Logout
</a>
</div>
) : (
<a className="button-lg" style={{cursor: "pointer"}} onClick={() => login()}>
Login
</a>
)
}
</div>
)
}
Additionally, we want to display different menu items. For this create a new file src/components/MenuBar.js
:
import {useFusionAuth} from "@fusionauth/react-sdk";
import {NavLink} from "react-router-dom";
export default function MenuBar() {
const {isAuthenticated} = useFusionAuth();
return (
<div id="menu-bar" className="menu-bar">
{
isAuthenticated ? (
<>
<NavLink to="/make-change" className="menu-link" activeClassName="active">Make Change</NavLink>
<NavLink to="/account" className="menu-link" activeClassName="active">Account</NavLink>
</>
) : (
<>
<a className="menu-link">About</a>
<a className="menu-link">Services</a>
<a className="menu-link">Products</a>
<a className="menu-link" style={{textDecorationLine: "underline"}}>Home</a>
</>
)
}
</div>
)
}
The next step is to tie it all together. Update the src/App.js
file by replacing its content with the following code:
import './App.css';
import HomePage from "./pages/HomePage";
import AccountPage from "./pages/AccountPage";
import {Navigate, Route, Routes} from "react-router-dom";
import {useFusionAuth} from "@fusionauth/react-sdk";
import LogoHeader from "./components/LogoHeader";
import MenuBar from "./components/MenuBar";
import MakeChangePage from "./pages/MakeChangePage";
function App() {
const {isLoading} = useFusionAuth();
if (isLoading) {
return null;
}
return (
<div id="page-container">
<div id="page-header">
<LogoHeader/>
<MenuBar/>
</div>
<div style={{flex: 1}}>
<Routes>
<Route path="/" element={<HomePage/>}/>
<Route path="/account" element={<AccountPage/>}/>
<Route path="/make-change" element={<MakeChangePage/>}/>
<Route path="*" element={<Navigate to="/"/>}/>
</Routes>
</div>
</div>
);
}
export default App;
Running the Application
You can now run the application with the following command:
npm start
You can now open up an incognito window and navigate to http://localhost:3000. You will be greeted with the home page. Log in with the user account you created when setting up FusionAuth, and you’ll be redirected to the account page.
The username and password of the example user
can be found in the FusionAuth via Docker section at the top of this article.
Next Steps
This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you’re going to want to do.
FusionAuth Customization
FusionAuth gives you the ability to customize just about everything with the user’s experience and your application’s integration. This includes
- Hosted pages such as login, registration, email verification, and many more
- Email templates
- User data and custom claims in access token JWTs
Security
- You may want to customize the token expiration times and policies in FusionAuth
- You can use the hosted backend pages to run this example without your own OAuth backend, but review the hosted backend docs to be aware of the limitations of this approach
- Choose password rules and a hashing algorithm that meet your security needs
Tenant and Application Management
- Model your application topology using Applications, Roles, Groups, Entities, and more
- Set up MFA, Social login, and/or SAML integrations
- Integrate with external systems using Webhooks, SCIM, and Lambdas