Passwordless face login with Entry in the React app
Tags: react, tutorial, biometrics, security
Published: 2022-05-11 16:47:46

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](https://docs.getentry.com/). And we'll dive straight into the code and build a protected OIDC app.

Or jump to the code, in the [repo](https://github.com/XIX-Entry/quickstarts/tree/master/react-typescript).

# 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
```

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
```
and populate it with 
```tsx
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"
};
```

# App's code
Now we need to prepare our `index.tsx` file to serve us right:

```tsx
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>
);
```

And `app.tsx` should follow along:
```tsx
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;
```

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
```

`login.tsx` should look like:
```tsx
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>
}
```

And `profile.tsx` should look like:
```tsx
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>
      );
}
```

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
```
And the content there will be:
```tsx
export * from './login';
export * from './profile';
```

# 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](https://github.com/XIX-Entry/quickstarts/tree/master/react-typescript)

Having troubles with the setup or questions, join our [Discord server](https://discord.com/channels/968124564095762542/968125102690541588)

Cheers!