SPA

React

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.

UserApplicationView HomepageClick Login LinkShow Login FormFill Out and Submit Login FormAuthenticates UserDisplay User's Account or OtherInfoUserApplication

Request flow during login before FusionAuth

The login flow will look like this after FusionAuth is introduced.

UserApplicationFusionAuthView HomepageClick Login Link (to FusionAuth)View Login FormShow Login FormFill Out and Submit Login FormAuthenticates UserGo to Redirect URIRequest the Redirect URIUser is AuthenticatedDisplay User's Account or OtherInfoUserApplicationFusionAuth

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

You'll find a Docker Compose file (docker-compose.yml) and an environment variables configuration file (.env) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

docker compose up -d

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 your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing docker compose down -v, then re-run docker compose up -d.

FusionAuth will be initially 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 is password.
  • Your admin username is admin@example.com and the password is password.
  • The base URL of FusionAuth is http://localhost:9011/.

You can log in to the FusionAuth admin UI and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the FusionAuth app. The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the View icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the OAuth configuration section.

The .env file contains passwords. In a real application, always add this file to your .gitignore file and never commit secrets to version control.

Create a basic React application

Now you are going to create a basic React application using the Vite. While this section builds a simple React application, you can use the same configuration to integrate your existing React application with FusionAuth.

npm create vite@latest changebank -- --template react-ts && cd changebank

The fusionauth server is configured with the valid redirect URI of localhost:3000. Ensure your changebank app runs on this port by adding server: { port: 3000 } to your vite.config.ts.

We are going to use the Hosted Backend feature of FusionAuth, so you don’t need to worry about setting up a backend server.

While this example uses localhost for your application and FusionAuth, there are complications if you plan to deploy using FusionAuth Cloud.

When developing against a FusionAuth Cloud instance with a hostname ending in fusionauth.io, unless your application shares the same domain of fusionauth.io attempts to use these endpoints will fail with a 403 status code.

These endpoints do not work correctly for cross origin requests. Cross origin requests occur when the application making the request to FusionAuth is using a separate domain. For example, if your application URL is app.acme.com and the FusionAuth URL is acme.fusionauth.io requests from your application to FusionAuth will be considered cross origin.

If possible, have FusionAuth and your application served by the same domain, using a proxy if needed. For example, serve your app from app.acme.com and FusionAuth from auth.acme.com.

If this configuration is not possible, use one of these alternative methods:

Modifying FusionAuth CORS configuration options does not fix this issue because the cookies that FusionAuth writes will not be accessible cross domain.

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, client Id, and redirect URI. You can do this by updating src/main.tsx:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import {
  FusionAuthProvider,
  FusionAuthProviderConfig,
} from "@fusionauth/react-sdk";

const config: FusionAuthProviderConfig = {
  clientId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
  redirectUri: "http://localhost:3000",
  postLogoutRedirectUri: "http://localhost:3000/logged-out",
  serverUrl: "http://localhost:9011",
  shouldAutoFetchUserInfo: true,
  shouldAutoRefresh: true,
  onRedirect: (state?: string) => {
    console.log(`Redirect happened with state value: ${state}`);
  },
  scope: 'openid email profile offline_access'
};

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <FusionAuthProvider {...config}>
        <App />
      </FusionAuthProvider>
    </BrowserRouter>
  </React.StrictMode>
);

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.tsx:

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 { isLoggedIn, isFetchingUserInfo, startLogin, startRegister } =
    useFusionAuth();

  useEffect(() => {
    if (isLoggedIn) {
      navigate("/account");
    }
  }, [isLoggedIn, navigate]);

  if (isLoggedIn || isFetchingUserInfo) {
    return null;
  }

  return (
    <div className="column-container">
      <div className="content-container">
        <div>
          <h1>Welcome to Changebank</h1>
            <p>
            Login or create a new account to get started
            </p>
            <button
              className="button-lg"
              style={{ cursor: "pointer" }}
              onClick={() => startLogin("state-from-login")}
            >
              Login
            </button>
            <br/>
            <button
              className="button-redirect"
              style={{ cursor: "pointer" }}
              onClick={() => startRegister("state-from-register")}
            >
              Create a new account.
            </button>
        </div>
      </div>

      <div className="image-container">
        <img src={background} alt="Coins" />
      </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.tsx:

import { useFusionAuth } from "@fusionauth/react-sdk";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

const 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 { isLoggedIn, isFetchingUserInfo } = useFusionAuth();

  useEffect(() => {
    if (!isLoggedIn) {
      navigate("/");
    }
  }, [isLoggedIn, navigate]);

  if (!isLoggedIn || isFetchingUserInfo) {
    return null;
  }

  return (
    <div className="app-container">
      <div>
        <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.tsx:

import { useFusionAuth } from "@fusionauth/react-sdk";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

const dollarUS = Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  useGrouping: false,
});

type Change = { total: number; nickels: number; pennies: number } | null;

export default function MakeChangePage() {
  const [amount, setAmount] = useState<number>(0);
  const [change, setChange] = useState<Change>(null);

  const navigate = useNavigate();

  const { isLoggedIn, isFetchingUserInfo } = useFusionAuth();

  useEffect(() => {
    if (!isLoggedIn) {
      navigate("/");
    }
  }, [isLoggedIn, navigate]);

  const makeChange = (e: React.MouseEvent<HTMLFormElement>) => {
    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 (!isLoggedIn || isFetchingUserInfo) {
    return null;
  }

  return (
    <div className="app-container">
      <form className="change-container" onSubmit={makeChange}>
        <h3>Make Change</h3>
        <div className="change-label">Amount in USD</div>
        <input
          className="change-input"
          name="amount"
          value={amount}
          onChange={(e) => setAmount(parseFloat(e.target.value))}
          type="number"
          step=".01"
        />
        <input className="change-submit" type="submit" value="Make Change" />
      </form>
      {change && (
        <div className="change-message">
          We can make change for {dollarUS.format(change.total)} with{" "}
          {change.nickels} nickels and {change.pennies} pennies!
        </div>
      )}
    </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.tsx:

import { useFusionAuth } from "@fusionauth/react-sdk";
import changebankLogo from "../assets/changebank.svg";

export default function LogoHeader() {
  const { isLoggedIn, userInfo, startLogin, startLogout } = useFusionAuth();

  return (
    <div id="logo-header">
      <img src={changebankLogo} alt="Change Bank" width="259" height="70" />
      {isLoggedIn ? (
        <div className="h-row">
          <p className="header-email">{userInfo?.email}</p>
          <button
            className="button-lg"
            style={{ cursor: "pointer" }}
            onClick={() => startLogout()}
          >
            Logout
          </button>
        </div>
      ) : (
        <button
          className="button-lg"
          style={{ cursor: "pointer" }}
          onClick={() => startLogin("state-from-login")}
        >
          Login
        </button>
      )}
    </div>
  );
}

Additionally, we want to display different menu items. For this create a new file src/components/MenuBar.tsx:

import { useFusionAuth } from "@fusionauth/react-sdk";
import { NavLink } from "react-router-dom";

export default function MenuBar() {
  const { isLoggedIn } = useFusionAuth();

  return (
    <div id="menu-bar" className="menu-bar">
      {isLoggedIn ? (
        <>
          <NavLink to="/account" className="menu-link">
            Account
          </NavLink>
          <NavLink to="/make-change" className="menu-link">
            Make Change
          </NavLink>
        </>
      ) : (
        <>
          <button
            className="menu-link"
            style={{ textDecorationLine: "underline" }}
          >
            Home
          </button>
          <button className="menu-link">Products</button>
          <button className="menu-link">Services</button>
          <button className="menu-link">About</button>
        </>
      )}
    </div>
  );
}

The next step is to tie it all together. Update the src/App.tsx file by replacing its content with the following code:

import { Navigate, Route, Routes } from "react-router-dom";

import LogoHeader from "./components/LogoHeader";
import MenuBar from "./components/MenuBar";
import HomePage from "./pages/HomePage";
import AccountPage from "./pages/AccountPage";
import MakeChangePage from "./pages/MakeChangePage";

import { useFusionAuth } from "@fusionauth/react-sdk";

function App() {
  const { isFetchingUserInfo } = useFusionAuth();

  if (isFetchingUserInfo) {
    return null;
  }

  return (
    <div id="page-container">
      <div id="page-header">
        <LogoHeader />
        <MenuBar />
      </div>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/logged-out" element={<HomePage />} />
          <Route path="/account" element={<AccountPage />} />
          <Route path="/make-change" element={<MakeChangePage />} />
          <Route path="*" element={<Navigate to="/" />} />
        </Routes>
    </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.

Made it this far? Want a free t-shirt? We got ya.

Thank you for spending some time getting familiar with FusionAuth.

*Offer only valid in the United States and Canada, while supplies last.

fusionauth tshirt

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 to do with the user's experience and the integration of your application. This includes:

Security

Tenant and Application Management