User Authentication in React & React Native with Easybase

easybase blog splash image

Introduction

React and React Native are powerful tools that can create beautiful, stateful interfaces and applications. Creating these apps, whether they be mobile or web-based, can become quite intricate. This is especially true when it comes to implementing modules such as user authentication; a sign-in and sign-up system where users can create accounts that can be managed by administrators.

Easybase makes this process simple with the <Auth /> and <NativeAuth /> components that integrate painlessly with Easybase’s React-friendly platform. These components can be customized with different themes and styles, so your application can have a unique look.

For those looking to manually implement a login interface, the last section of this piece demonstrates how to easily accomplish this with the provided user functions like signIn, signOut, forgotPassword, and more.


<Auth />

Setting up a React project with Easybase is simple. Use this reference to get up and running in just a couple of minutes.

Once that’s finished, open up App.js in your React project (for React Native take a look at the next section). Ensure that your ebconfig file is in the src/, next to App.js:

├── ...
├── node_modules/
├── package.json
├── public/
└── src/
    ├── ebconfig.js ←
    ├── App.js
    └── index.js

Start by importing some essential items:

import { EasybaseProvider, Auth } from 'easybase-react'
import ebconfig from './ebconfig'

EasybaseProvider will grant access to the functions necessary to perform user operations in the front-end code. The Auth component handles these functions automatically, with a customizable interface. Lastly, ebconfig is the unique token that connects your front-end to Easybase, downloaded from the Projects tab. Be sure to set and save the proper table permissions:

Easybase user authentication 1

The components in Auth will only be accessible to users who are signed in. Wrap all of that in EasybaseProvider (with ebconfig) like so:

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth>
        <p>Congrats, you're in!</p>
      </Auth>
    </EasybaseProvider>
  )
}

Save that and run your application by executing npm run start in your project directory.

Easybase user authentication 2

Navigate to the sign-up page by clicking the secondary button under ‘Continue’. Fill out that form and click ‘Continue’.

Note: a real email should be used for the Forgot Password workflow to work properly

Easybase user authentication 3

Congrats! You’re application is successfully using user authentication using JWTs with local caching.


<NativeAuth />

This section is for React Native. Otherwise, continue to the next section.

React Native apps utilize Easybase’s NativeAuth component which works almost exactly the same as the standard Auth component. This can be accessed via the easybase-react/native directory.

To set up a new React Native project with Easybase, use npx create-react-native-app and follow the Project material here.

Since there is no src/ folder when using npx create-react-native-app, place your ebconfig file in the root folder, next to App.js:

├── node_modules/
├── package.json
├── ios/
├── android/
├── ebconfig.js ←
├── App.js
├── index.js
└── ...

Your React Native application code, using the <NativeAuth /> component, would look like the following:

import React from 'react'
import { Text } from 'react-native'
import { EasybaseProvider } from 'easybase-react'
import { NativeAuth } from 'easybase-react/native'
import ebconfig from './ebconfig'

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <NativeAuth>
        <Text>Congrats, you're in!</Text>
      </NativeAuth>
    </EasybaseProvider>
  )
}

Easybase user authentication 4

Although the rest of this article will demonstrate the <Auth /> component, understand that these two components work almost identically. Furthermore, they have the same props that perform the same functions, respectively. For more technical information, check out the source on GitHub.


Sign Out

After a new user signs up, they are brought to the components inside the <Auth /> component. You’re probably going to want to head back to your <Auth /> component at this point, but refreshing the page will not bring you back – the browser instance will still be signed in. This is a feature and occurs because the easybase-react library stores verification and refresh tokens on the user device to keep them temporarily signed-in, as most modern applications do.

Let’s create a small component that allows users to sign out. To do this, import useEasybase, which provides the functions used to communicate with your authentication instance and database.

// ...
import { EasybaseProvider, Auth, useEasybase } from 'easybase-react'
// ...

const SignOutButton = () => {
  const { signOut } = useEasybase()

  return (<button onClick={signOut}>Sign Out</button>) // <-
}

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth>
        <p>Congrats, you're in!</p>
        <SignOutButton /> {/* <- */}
      </Auth>
    </EasybaseProvider>
  )
}

Clicking this new button will sign out the current user, invalidate their cached tokens, and return the <Auth /> component.

Here are some of the other user-related functions returned from that hook, that you may find useful to use in your front-end code: signOut, signIn, signUp, isUserSignedIn, forgotPassword, forgotPasswordConfirm, getUserAttributes, resetUserPassword, and much more.


Sign Up Fields

To customize which fields are present in the sign up view, use the signUpFields prop of Auth. This prop takes an object, mapping the input name to an object with some constraints and a corresponding error message to display when constraints are not met.

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth
        signUpFields={{
          phoneNumber: {
            required: {
              value: true,
              message: "Please fill out 'Phone Number'"
            }
          }
        }}
      >
        <p>Congrats, you're in!</p>
        <SignOutButton />
      </Auth>
    </EasybaseProvider>
  )
}

Easybase user authentication 5

Various fields are available and the types of constraints allowed can be seen in React Hook Form’s Register documentation (be sure to enable ‘Register with validation and error message’ under ‘Register Options’).

You can also just pass true, for optional fields:

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth signUpFields={{ gender: true }}>
        <p>Congrats, you're in!</p>
        <SignOutButton />
      </Auth>
    </EasybaseProvider>
  )
}

These extra fields will appear as ‘User Attributes’ under the Users tab in Easybase

Easybase user authentication 6

Theming

Developers often strive for their apps to have a unique look and feel. There are two ways to style the <Auth /> component:

  1. The customStyles prop for specific component CSS
  2. The theme prop for general styling

Under the hood, themes are just large customStyles sheets. For example, take a look at the source for the ‘minimal-dark’ theme.

Here’s an example that utilizes both methods to create a completely different UI look and feel:

export default function App() {
  const myStyle = {
    container: {
      backgroundColor: '#eceff1',
    },
    form: {
      backgroundColor: '#fff',
      border: "2px solid #bcb7ce7f",
      borderRadius: 0,
      boxShadow: 'none'
    },
    textField: {
      fontSize: 16
    }
  }

  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth theme="material" customStyles={myStyle}>
        <p>Congrats, you're in!</p>
        <SignOutButton />
      </Auth>
    </EasybaseProvider>
  )
}

Easybase user authentication 7

All custom styling options can be seen in the Easybase documentation for IStyles. Themes are unavailable for React Native, but use INativeStyles for Native customStyles options.


Dictionary

To change the specific language used in the interface, use the dictionary prop to map the predefined labels to new string values.

Here’s an example that uses dictionary to do the following:

  • Change the label “Sign in to your account” to “Welcome to React-flix!”
  • Change the Email input placeholder, “Email” to “iCloud Name *”
export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <Auth dictionary={{
        signInHeader: "Welcome to React-flix!",
        emailLabel: "iCloud Name *"
      }}>
        <p>Congrats, you're in!</p>
        <SignOutButton />
      </Auth>
    </EasybaseProvider>
  )
}

Utilizing this feature, along with the styling options demonstrated above, give developers great control over their application’s sign-in/sign-up interface.

Easybase user authentication 8

These dictionary changes are available throughout the interface. Take a look at the IDictionary type for all options.

Manual Implementation

It may be that case that <Auth /> or <NativeAuth />, even with the customization options, do not give you enough control over the workflow of your application’s interface. The useEasybase hook provides all the necessary functions to implement an authentication interface. In fact, <Auth /> and <NativeAuth /> work by using this hook, so take a look at that source code as a reference.

Let’s create our own component called <MyAuth />. This component will have two features. One is a button in the top-right corner of the app that says ‘Sign In’ or ‘Sign Out’ based on the user’s state. The other being a modal that will appear above our application when a user clicks ‘Sign In’. The modal will feature a username and password text field and buttons to sign in or sign up. Use the isUserSignedIn() function to conditionally render components and check the user’s current authentication status:

/*
Example App.css:

.authButton {           .authDialog {                       .authDialog div {
  position: absolute;     position: fixed;                    padding: 20px;
  top: 10px;              top: 0;                             background-color: white;
  right: 50px;            bottom: 0;                          display: flex;
  width: 100px;           left: 0;                            flex-direction: column;
  height: 50px;           right: 0;                         }
  font-size: 15px;        background: rgba(0, 0, 0, 0.7);
}                         transition: opacity 500ms;
                          visibility: hidden;
                          opacity: 0;
                          display: flex;
                          justify-content: center;
                          align-items: center;
                        }
*/

// ...
import { useState } from 'react'
import './App.css'
// ...

function MyAuth({ children }) {
  const {
    isUserSignedIn,
    signIn,
    signOut,
    signUp
  } = useEasybase();

  const [dialogOpen, setDialogOpen] = useState(false);
  const [usernameValue, setUsernameValue] = useState("");
  const [passwordValue, setPasswordValue] = useState("");

  const onAuthButtonClick = () => {
    if (isUserSignedIn()) {
      signOut();
    } else {
      setDialogOpen(true);
    }
  }

  const onSignInClick = async () => {
    const res = await signIn(usernameValue, passwordValue);
    if (res.success) {
      setDialogOpen(false);
      setUsernameValue("");
      setPasswordValue("");
    }
  }

  const onSignUpClick = async () => {
    const res = await signUp(usernameValue, passwordValue);
    if (res.success) {
      await signIn(usernameValue, passwordValue);
      setDialogOpen("");
      setUsernameValue("");
      setPasswordValue("");
    }
  }

  if (isUserSignedIn()) {
    return (
      <>
        <button onClick={onAuthButtonClick} className="authButton">Sign Out</button>
        {children}
      </>
    )
  } else return (
    <>
      <button onClick={onAuthButtonClick} className="authButton">Sign In</button>
      <div className="authDialog" style={dialogOpen ? { opacity: 1, visibility: 'visible' } : {}}>
        <div>
            <input type="text" placeholder="Username" value={usernameValue} onChange={e => setUsernameValue(e.target.value)} />
            <input type="password" placeholder="Password" value={passwordValue} onChange={e => setPasswordValue(e.target.value)} />
            <div>
              <button onClick={onSignInClick}>Sign In</button>
              <button onClick={onSignUpClick}>Sign Up</button>
            </div>
        </div>
      </div>
    </>
  )
}

Note that the styling of this component is extremely simple for sake-of-brevity

Finally, we can replace <Auth /> with our new component, <MyAuth />:

export default function App() {
  return (
    <EasybaseProvider ebconfig={ebconfig}>
      <MyAuth>
        <p>Congrats, you're in!</p>
      </MyAuth>
    </EasybaseProvider>
  )
}

Just like that, your application supports a stateful user authentication workflow.

Note that the signIn, signOut, and signUp functions are asynchronous, so you can use await or .then to handle the completion of these events.

Easybase user authentication 9

This example doesn’t add any user attributes, but these can be assigned in a couple of different ways:

  1. Manually, in one of the rows under the Users tab in Easybase.
  2. Pass an object in signUp(newUserID, password, { firstName: "bob" }).
  3. If a user is signed in, setUserAttribute("firstName", "bob")

You can then retrieve these objects programmatically with getUserAttributes(). User attributes are not for storing user-corresponding application data, rather it’s used for simple metadata such as createdAt or firstName.

For more information on manually implementing login authentication in React Native, take a look at Michael’s article in freeCodeCamp.

Published: Jun 07, 2021