DEV Community

Cover image for Create a React App with Biometric Authentication
Chris Loper for Passage

Posted on

Create a React App with Biometric Authentication

Passage offers developers a frictionless process to implement passwordless user authentication into their application. In this guide, learn how to build a simple React application that uses Passage for biometric authentication.

Users will log in to your application using the biometrics built into their devices (e.g. FaceID, TouchID, Windows Hello, etc) or with magic links sent to their email. The app will be built such that authenticated users can view a simple dashboard, while unauthenticated users will be blocked. This article will walk you through how to create a React app, setup the components, and authenticate users with Passage.

If you are already familiar with React, you can go straight to our full example application on GitHub or skip to "Add User Authentication with Passage" to learn how to integrate biometric authentication into an existing application.

Setup

You will need to have Node >= 14.0.0 and npm >= 5.6 on your machine. To create a React app run:

npx create-react-app passage-app
cd passage-app
Enter fullscreen mode Exit fullscreen mode

Now install the router (react-router-dom):

npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Build Views for the Application

The router is used to navigate between the views in your application. In this application, we will have two views Home.js and Dashboard.js. The Home view will contain Passage’s <passage-auth> custom element, which enables user authentication. The Dashboard view will be an authenticated route and will block any unauthenticated users from viewing the dashboard.

mkdir src/views
touch src/views/Home.js
touch src/views/Dashboard.js
Enter fullscreen mode Exit fullscreen mode

Lets start with building the Home view src/views/Home.js:

function Home() {
    return (
        <div className="home-div">
                    This is the Home View!
        </div>
    );

}

export default Home;
Enter fullscreen mode Exit fullscreen mode

Now we will build a simple Dashboard view src/views/Dashboard.js:

function Dashboard() {
      return (
          <div className="dashboard-div">
               Welcome!
          </div>
      );
}

export default Dashboard;
Enter fullscreen mode Exit fullscreen mode

Setup the Router in app.js and index.js

Let’s edit src/app.js, which is the entry point for the React app. Configure the router to have routes to both the Home view and the Dashboard view:

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

import Home from "./views/Home";
import Dashboard from "./views/Dashboard";

function App() {
  return (
      <div>
            <div className="main-container">
                <Routes>
                    <Route path="/" element={<Home/>}></Route>
                    <Route path="/dashboard" element={<Dashboard/>}></Route>
                </Routes>
            </div>
      </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Also, edit src/index.js to add the router to the application:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app';
import { BrowserRouter as Router } from "react-router-dom";

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Now let’s create a Banner component that we can add to src/app.js, making it visible throughout the application. Let’s create a component directory and create the banner.js file. Also, let’s create a styles directory and create a Banner.module.css file to add CSS styling to the Banner component.

mkdir src/components
touch src/components/banner.js
mkdir src/styles
touch src/styles/Banner.module.css
Enter fullscreen mode Exit fullscreen mode

Lets build src/components/banner.js with this code:

import styles from '../styles/Banner.module.css';

function Banner() {
    return ( 
        <div className={styles.mainHeader}>
            <a href="https://passage.id/" ><div className={styles.passageLogo}></div></a>
            <div className={styles.headerText}>Passage + React Example App</div>
            <div className={styles.spacer}></div>
            <a href="https://passage.id/" ><span className={styles.text}>Go to Passage</span></a>
        </div>
    );
}
export default Banner;
Enter fullscreen mode Exit fullscreen mode

Copy this CSS into src/styles/Banner.module.css to add styling to the above component:

.mainHeader{
    padding: 20px 30px;
    display: flex;
    align-items: center;
    background-color: #27417E;
    color: white;
}
.header-text {
    font-size: 24px;
    margin-left: 10px;
}

.passageLogo {
        background-image: url('https://storage.googleapis.com/passage-docs/passage-logo.svg');
    background-repeat: no-repeat;
    width: 60px;
    height: 60px;
    cursor: pointer;
}
.spacer {
    flex-grow: 1;
}

.link {
    margin-left: 20px;
    color: white;
    text-decoration-color: white;
}
Enter fullscreen mode Exit fullscreen mode

Now let’s add the Banner component to src/app.js so that it will appear throughout the app:

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

import Home from "./views/Home";
import Dashboard from "./views/Dashboard";
import Banner from "./components/banner";

function App() {
  return (
      <div>
            <Banner/>
            <div className={styles.mainContainer}>
                <Routes>
                    <Route path="/" element={<Home/>}></Route>
                    <Route path="/dashboard" element={<Dashboard/>}></Route>
                </Routes>
            </div>
      </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now lets add a footer and some styling to src/app.js:

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

import Home from "./views/Home";
import Dashboard from "./views/Dashboard";
import Banner from "./components/Banner";
import styles from './styles/App.module.css';

function App() {
  return (
      <div>
            <Banner/>
            <div className={styles.mainContainer}>
                <Routes>
                    <Route path="/" element={<Home/>}></Route>
                    <Route path="/dashboard" element={<Dashboard/>}></Route>
                </Routes>
            </div>
            <div className={styles.footer}>
                Learn more with our <a href="https://docs.passage.id">Documentation</a> and <a href="https://github.com/passageidentity">Github</a>.      
            </div>
      </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

And the styling lives in src/styles/App.module.css

.mainContainer {
    background: white;
    box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
    border-radius: 20px;
    width: 310px;
    min-height: 310px;
    margin: 30px auto;
}
.footer {
    text-align: center;
    font-size: 18px;
}
Enter fullscreen mode Exit fullscreen mode

Add User Authentication with Passage

Now it is time to add passwordless authentication to your application using Passage. First, install the Passage custom element from npm:

npm install --save @passageidentity/passage-auth
Enter fullscreen mode Exit fullscreen mode

Then import the package in the view where you intend to authenticate users. In this case, src/views/Home.js

**import** '@passageidentity/passage-auth'
Enter fullscreen mode Exit fullscreen mode

Importing this script will register the Passage custom element for use in your React view. For more information about custom elements refer to the online documentation.

Create an application in the Passage Console (here) with the following settings:

  • Authentication Origin: http://localhost:3000
  • Redirect URL: /dashboard

Below is a screenshot from creating a Passage App in the Passage Console:
Image description

Once the application has been created, copy the Application ID from the console into a .env file in the root of React app’s repository.

# .env
REACT_APP_PASSAGE_APP_ID=<Your New Passage App ID Here>
Enter fullscreen mode Exit fullscreen mode

Now lets add the passage-auth custom element to src/views/Home.js

import "@passageidentity/passage-auth";

function Home() {
    return (
        <passage-auth app-id={process.env.REACT_APP_PASSAGE_APP_ID}></passage-auth>
    );

}

export default Home;
Enter fullscreen mode Exit fullscreen mode

Verifying User Authentication

Now it is time to prevent unauthenticated users from accessing the dashboard page. Please keep in mind that this dashboard protection will not protect sensitive API endpoints. Your server should always use one of the Passage backend libraries to authorize users before returning sensitive data.

For the simplicity of this example, let’s focus on the frontend code.

Instead, implement this check by creating a hook to authenticate the current Passage user. This hook contains a reusable piece of stateful logic that allows any component to check if the current user is authenticated and return basic user info that Passage stores for you.

Create a directory for hooks and create a file called useCurrentUser.js:

mkdir src/hooks
touch src/hooks/userCurrentUser.js
Enter fullscreen mode Exit fullscreen mode

The code for this hook is as follows:

import { useState, useEffect } from "react";
import { PassageUser } from '@passageidentity/passage-auth/passage-user';

export function useCurrentUser() {
    const [result, setResult] = useState({
        isLoading: true,
        isAuthorized: false,
        username: '',
    });

    useEffect(() => {
        let cancelRequest = false;
        new PassageUser().userInfo().then(userInfo=> {
            if( cancelRequest ) {
                return;
            }
            if(userInfo === undefined){
                setResult({
                    isLoading: false,
                    isAuthorized: false,
                    username: "",
                });
                return;
            }
            setResult({
                isLoading: false,
                isAuthorized: true,
                username: userInfo.email ? userInfo.email : userInfo.phone,
            });
        });
        return () => {
            cancelRequest = true;
        };
    }, []);
    return result;
}
Enter fullscreen mode Exit fullscreen mode

Note: The React useEffect() hook happens on both mount and unmount, so canceling the API request will prevent an error that can occur when handling API requests in a useEffect hook that occurs when setting the state on a component that is unmounted.

Now that this hook has been implemented, move back to src/view/Dashboard.js and use this hook to verify that a user has been properly authenticated.

import {useCurrentUser} from '../hooks/useCurrentUser';
import styles from '../styles/Dashboard.module.css';

function Dashboard() {
    const {isLoading, isAuthorized, username} = useCurrentUser();

    if (isLoading) {
        return null;
    }
    const authorizedBody = 
    <>
        You successfully signed in with Passage.
        <br/><br/>
        Your email is: <b>{username}</b>
    </>

    const unauthorizedBody = 
    <>
        You have not logged in and cannot view the dashboard.
        <br/><br/>
        <a href="/" className={styles.link}>Login to continue.</a>
    </>

    return (
        <div className={styles.dashboard}>
            <div className={styles.title}>{isAuthorized ? 'Welcome!' : 'Unauthorized'}</div>
            <div className={styles.message}>
                { isAuthorized ? authorizedBody : unauthorizedBody }
            </div>
        </div>
    );

}

export default Dashboard;
Enter fullscreen mode Exit fullscreen mode

Now unauthenticated users that visit /dashboard will be shown an unauthorized page, while authenticated user will be shown their Passage User ID.

Note: Notice that styles have also been added to this version of src/views/Dashboard.js A copy of src/styles/Dashboard.module.csscan be found below:

.dashboard{
    padding: 30px 30px 20px;
}
.title {
    font-size: 24px;
    font-weight: 700;
    margin-bottom: 30px;
}
.message {
    overflow-wrap: anywhere;
}
.link {
    color: black;
    text-decoration-color: black;
}
Enter fullscreen mode Exit fullscreen mode

Now run the app, to see Passage biometric authentication in action:

npm run start
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You now have a React application that utilizes passwordless authentication. Biometric authentication, as you hopefully can see, is both frictionless to implement with Passage and effortless for end users to authenticate themselves.

You can see the finished application on GitHub here.

Learn More About Passage

To learn more about Passage and biometric authentication for web applications, you can:

  • Explore our dashboard to view and create users, customize your application, and add friends
  • Read our guides for other tech stacks and learn how to authorize requests in your backend server
  • Join our Discord and say hi

‍Passage is in beta and actively seeking feedback on the product. If you have feedback, bug reports, or feature requests, we would love to hear from you. You can email us at support@passage.id or fill out this form.

This article was originally published on the Passage blog.

Top comments (0)