Passwordless face login with Entry in the React app

asashay

Alex Oliynyk

Posted on May 11, 2022

Passwordless face login with Entry in the React app

Entry solves the problem of user Identity. Imagine an app where you do not need to bother about duplicated users, account theft or going into hustle of ensuring that only owner can access an account. That's all possible with Entry and its biometrics authentication engine.

You could read more about it in the docs. And we'll dive straight into the code and build a protected OIDC app.

Or jump to the code, in the repo.

Setup

Let's create a React app, clean it up a bit and install the dependencies:

npx create-react-app entry-demo --template typescript
cd entry-demo
npm install react-oidc-context

rm ./src/App.css ./src/App.test.tsx ./src/logo.svg
mv ./src/App.tsx ./src/app.tsx
Enter fullscreen mode Exit fullscreen mode

To use Entry we need to:

  1. Register on Entry. I can be done on https://app.xix.ai/.
  2. Login to Entry and create new Public workspace at https://app.xix.ai/workspace/create-new.
  3. Create an oidc-connect app at https://app.xix.ai/workspace/alex-test-ws1/admin/apps/new
  4. In the app's config add http://localhost:3000/* to the Valid Redirect URIs (comma-separated) field and http://localhost:3000 to the Web Origins (comma-separated) field.

OIDC client configuration

We will start creating the OIDC config file:

touch oidc-config.ts
Enter fullscreen mode Exit fullscreen mode

and populate it with

const url = window.location.origin;

export const oidcConfig = {
  authority: "https://entry.xix.ai/auth/realms/YOUR_WORKSPACE_NAME",
  client_id: "YOUR_APP_ID",
  client_secret: 'YOUR_CLIENT_SECRET',
  redirect_uri: url,
  post_logout_redirect_uri: url,
  response_type: "code",
  scope: "openid profile email"
};
Enter fullscreen mode Exit fullscreen mode

App's code

Now we need to prepare our index.tsx file to serve us right:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { AuthProvider } from "react-oidc-context";
import App from './app';

import { oidcConfig  } from './oidc-config';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <AuthProvider {...oidcConfig} response_mode="fragment">
      <div style={{ 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center', 
        height: '100vh'
        }}>
        <App />
      </div>
    </AuthProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

And app.tsx should follow along:

import { useAuth } from "react-oidc-context";

import { LoginPage, ProfilePage } from './pages';

function App() {
    const auth = useAuth();

    const handleLogoutClick = () => auth.signoutRedirect();

    switch (auth.activeNavigator) {
        case "signinSilent":
          return <div>Signing you in...</div>;
        case "signoutRedirect":
            return <div>Signing you out...</div>;
    }

    if (auth.isLoading) {
      return <div>Loading...</div>;
    }

    if (auth.error) {
      return <div>
        <p>Oops... {auth.error.message}</p>
        <button 
          style={{ padding: '20px' }} 
          onClick={handleLogoutClick}>Go Back</button>
      </div>;
    }

    return auth.isAuthenticated ? <ProfilePage /> : <LoginPage />
}

export default App;
Enter fullscreen mode Exit fullscreen mode

There are several if ... else statements here which allow us to properly communicate to the user what is happening during the authentication.

Pages setup

mkdir pages
touch ./pages/login.tsx
touch ./pages/profile.tsx
Enter fullscreen mode Exit fullscreen mode

login.tsx should look like:

import { useAuth } from "react-oidc-context";

export function LoginPage() {
    const auth = useAuth();
    return <div>
      <button 
        onClick={() => void auth.signinRedirect({extraQueryParams: { prompt: 'login'}})}
        style={{ padding: '20px'}}
      >Log in</button>
    </div>
}
Enter fullscreen mode Exit fullscreen mode

And profile.tsx should look like:

import { useAuth } from "react-oidc-context";

export function ProfilePage() {
    const auth = useAuth();

    const handleLogoutClick = () => {
      auth.signoutRedirect();
    }

    return (
      <div style={{ display: 'flex', flexDirection: 'column', maxWidth: '320px'}}>
        <h3>Your user data:</h3>
        <pre style={{ padding: '20px'}}>
          {JSON.stringify(auth.user?.profile, null, 2)}
        </pre>
        <button style={{ padding: '20px'}} onClick={handleLogoutClick}>Log out</button>
      </div>
      );
}
Enter fullscreen mode Exit fullscreen mode

To make importing of the pages easier in the pages folder let's create index.tsx file and reexport everything from login.tsx and profile.tsx.

touch index.tsx
Enter fullscreen mode Exit fullscreen mode

And the content there will be:

export * from './login';
export * from './profile';
Enter fullscreen mode Exit fullscreen mode

What's next

Now that our app is secured we could start building pages and adding new features.

Users who will try to login to your app will be able to register and use it without the need to remember password. Neat, right?

Repo with the code

Having troubles with the setup or questions, join our Discord server

Cheers!

💖 💪 🙅 🚩
asashay
Alex Oliynyk

Posted on May 11, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related