Add Netlify Identity Authentication to any React App in 5 minutes with React Context, Hooks and Suspense

swyx - Jun 21 '19 - - Dev Community

💁🏼‍♂️Bottom Line Up Front: Here's a demo of the identity widget we'll see in this article, although you are free to write your own authentication UI!
💁🏼‍♂️If you want to skip the explanation and just get to the quickest start, skip to the React Netlify Identity Widget section below!

Adding authentication is a pain point for many React beginners. We’ve made it ridiculously easy to add Netlify Identity onto any React app, including create-react-app, Gatsby, Next.js, or any other setup you may have, by wrapping it all into one simple React Hook! However, this article is more about effective design patterns for introducing authentication into React apps, and any reader should be able to write similar wrappers for their preferred provider.

For the purposes of our examples though, we will use Netlify Identity. This is a very simple authentication service provided by Netlify, with a generous free tier. You can use this for gated content, site administration, authenticated functions, and more. Users primarily access this functionality through GoTrue-JS, the 3kb JS client for accessing Netlify Identity. Because it is just an open source API (that you can self-host), you don’t need to host your app on Netlify, nor even have a JAMstack app, to be able to use it.

Hooks and Context with react-netlify-identity

For React users, we recently wrapped up all the functionality of GoTrue-JS into a very easy to use React Hook. This made Netlify Identity dramatically easier to use by being a drop-in authentication solution for most React apps.

Assuming you have an existing Netlify site instance (if you don’t, you can set that up by clicking here) and have enabled Netlify Identity on it, you get started by installing:

    npm i react-netlify-identity
Enter fullscreen mode Exit fullscreen mode

The library has an IdentityContext internally, but you never have to manipulate it manually. Instead wrap the IdentityContextProvider around the root of your app:

import React from "react"
import { IdentityContextProvider } from "react-netlify-identity"
function App() {
  const url = "https://your-identity-instance.netlify.com/" // supply the url of your Netlify site instance with Identity enabled. VERY IMPORTANT
  return <IdentityContextProvider url={url}>{/* rest of your app */}</IdentityContextProvider>
}
Enter fullscreen mode Exit fullscreen mode

That’s all the setup you need!

Now you can use the exposed identity methods anywhere in your app (they are documented in the README, but also you can get autocompletion hints since the library is written in TypeScript):

import { useIdentityContext } from "react-netlify-identity"

// log in/sign up example
function Login() {
  const { loginUser, signupUser } = useIdentityContext()
  const formRef = React.useRef()
  const signup = () => {
    const email = formRef.current.email.value
    const password = formRef.current.password.value
    signupUser(email, password)
      .then((user) => console.log("Success! Signed up", user))
      .catch((err) => console.error(err))
  }
  // write similar logic for loginUser
  // return a form attached to formRef, with email and password fields
}
Enter fullscreen mode Exit fullscreen mode

Normally this is where I point you to a working demo with source code and leave you to “go forth and write your authenticated apps”, but even this is too much work to be done especially for “quick & easy” demos.

When we said 5 minutes, we meant 5 minutes.

Sidebar: Is that a Hook or a Context?

If you’re squinting at useIdentityContext and wondering what that is, you’re not alone. If it is a Context, why not export an IdentityContext so that the user can call useContext(IdentityContext)? If it is a Hook, why did you need to wrap an IdentityContextProvider at the app root in the first place?

Short answer: It’s both.

react-netlify-identity exports a Custom Provider and a Custom Consumer Hook, a pattern popularized by Kent C Dodds. The Custom Provider lets us initialize required info (the Netlify Identity instance) once, while The Custom Consumer Hook lets us take care of the nitty gritty details of null checks, as well as allows us to refine types for TypeScript users.

React.lazy and Suspense with react-netlify-identity-widget

Where react-netlify-identity exports reusable authentication behavior for your apps, it has no opinion at all on your authentication UI. This can halt your productivity while you futz around designing the auth UI of your dreams, meanwhile not getting feedback from real users on the core app or site that you actually want to show.

What react-netlify-identity-widget aims to do is to provide a “good enough” authentication UI for you to get going fast, while offering customizability in styling and being a drop-in solution on virtually any app. To be a drop-in solution, the best UI paradigm is using a modal, which comes with its own accessibility issues, so we lean on the excellent Reach UI project to provide accessible components.

To get going, install:

    ## this re-exports react-netlify-identity, no separate install needed
    npm i react-netlify-identity-widget
    ## peer dependencies, if you don't already have them
    npm i @reach/dialog @reach/tabs @reach/visually-hidden
Enter fullscreen mode Exit fullscreen mode

To use this widget, you set up the IdentityContextProvider exactly as above:

import { useIdentityContext, IdentityContextProvider } from "react-netlify-identity-widget"

function App() {
  const url = "https://your-identity-instance.netlify.com/"
  return <IdentityContextProvider value={url}>{/** rest of your app **/}</IdentityContextProvider>
}
export default App
Enter fullscreen mode Exit fullscreen mode

The only new things you need to do pertain to rendering the Modal widget, which the default export of the library, as well as (optionally) importing the CSS, if you don’t want to write your own. It is a controlled component, so you just need to pass in a boolean to showDialog to indicate if you want it open or closed (as well as give it an onCloseDialog callback to close itself):

import "react-netlify-identity-widget/styles.css"
// code split the modal til you need it!
const IdentityModal = React.lazy(() => import("react-netlify-identity-widget"))

function Main() {
  const identity = useIdentityContext()
  const [dialog, setDialog] = React.useState(false)
  const isLoggedIn = identity && identity.isLoggedIn
  return (
    <div className="App">
      <button className="btn" onClick={() => setDialog(isLoggedIn)}>
        {isLoggedIn ? "LOG OUT" : "LOG IN"}
      </button>
      <React.Suspense fallback="loading...">
        <IdentityModal showDialog={dialog} onCloseDialog={() => setDialog(false)} />
      </React.Suspense>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

What is that React.lazy function and React.Suspense component? These are relatively new React features for code splitting by making dynamically imported components declarative. This way, even though react-netlify-identity-widget is a trim 6kb min+gzipped, your user doesn’t pay unnecessary JS import cost until they try to log in, making your app that much snappier to load.

Run your app (example here), click your log in button, and get this modal:

The widget helps to bake in a litany of authentication UI standards you’ll want to consider (or avoid implementing for your MVP’s):

More importantly, it takes all the decision-making out of adding an authentication UI on top of your existing app at very little cost. To check against authentication information anywhere in your app (eg for Protected Routes or getting a canonical user ID), you simply call useIdentityContext just like before.

Conclusion

While this article uses Netlify Identity for its authentication provider, the design patterns we describe can easily be used by any other provider like Auth0, Okta, or one you roll yourself. We simply think these are excellent use cases for combining the best of React’s new features for a fantastic developer experience for authentication, traditionally a time-sucking, undifferentiated feature to add and do well. It is possible that this library may evolve to accept multiple adapters for authentication providers in future - if you are interested in collaborating on one unified API for all authentication in React, get in touch!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .