React authentication, simplified
Authentication is one of those things that just always seems to take a lot more effort than we want it to.
To set up auth, you have to re-research topics you haven't thought about since the last time you did authentication, and the fast-paced nature of the space means things have often changed in the meantime. New threats, new options, and new updates may have kept you guessing and digging through docs in your past projects.
In this tutorial, we lay out a different approach to authentication (and access control, SSO, and more) in React applications.
Rather than add a static library that you have to keep up to date or re-research each time you want to implement auth, we'll use a service that stays up to date automatically and is a simpler alternative to Auth0, Okta, and others.
React authentication
When writing authentication in React, our React app makes a request to our authentication server, which then returns an access token. That token is saved in the browser and can be used in subsequent requests to your server (or other servers, if needed).
Whether writing standard email & password authentication or using magic links or single sign on (SSO) logins like Google, Azure, or Facebook, we want our React app to send an initial request to an authentication server and have that server handle all the complexity of generating a token.
- Send the initial request to the authentication server
- Receive and store the access token
- Send the access token to your server with each subsequent request
JWTs (JSON Web Tokens)
JSON Web Tokens (JWTs) are compact, URL-safe tokens that can be used for authentication and access control in React applications.
Each JWT has a simple JSON-object as its "payload" and is signed such that your server can verify that the payload is authentic.
It's important to note that this payload is readable by anyone with the JWT, including your React application or a third party.
A JWT access token looks like this:
The payload for the JWT is the middle section (separated by periods):
The JWT payload can be decoded from base64 to yield the JSON object:
JWT access tokens
With modern RSA cryptography, an authentication server can generate secure JWTs, and any other machine can read and verify those JWTs using a public key.
Your backend server can verify a JWT access token as authentic by checking it against the public key.
This allows your backend server to reject any JWT access tokens that were not created by the authentication server (or that have expired).
- Your React app requests a JWT access token whenever the user wants to sign on.
- The authentication server generates a JWT access token using a private key and then sends the JWT access token back to your React app.
- Your React app stores this JWT access token and sends it to your backend server whenever your user needs to make a request.
- Your backend server verifies the JWT access token using a public key and then read the payload to determine which user is making the request.
Each of these steps is simple to write down, but each step has its own pitfalls when you actually want to implement it and keep it secure. Especially over time, as new threat vectors emerge and new platforms need to be patched or supported, the security overhead can add up quickly.
Userfront removes auth complexity in React apps
Userfront is a framework that abstracts away auth complexity. It is a fully-featured solution that stays up to date automatically.
This makes it much easier for you to work with authentication in a React application and, perhaps most importantly, keeps all the auth protocols updated for you over time.
Setting up authentication in React
Now we will go through building all the main aspects of authentication in a React application.
We will use Create React App for setup, along with React Router for client-side routing.
JWT access token flow
At a high level, React's responsibility in authentication is to:
-
Send an initial request to Userfront to get the JWT access token. This is what the signup and login forms do.
-
Send the JWT access token to your server with each subsequent request.
Set up Create React App
To get React up and running, start by installing Create React App(opens new window) and React Router(opens new window).
Now our React application is available at http://localhost:3000(opens new window)
Just like it says, we can now edit the src/App.js
file to start working.
Routing
We'll set up a small app with routing. This is all we need to start adding authentication.
Route | Description |
---|---|
/ | Home page |
/login | Login page |
/reset | Password reset page |
/dashboard | User dashboard, for logged in users only |
Replace the contents of src/App.js
with the following, based on the React Router quickstart:
With our routes in place, we are ready to add authentication.
Signup, login, and password reset
We'll start by adding a signup form to the home page.
In the Toolkit section of your dashboard, locate the instructions for installing your signup form.
It will look like this:
Keep your Toolkit open in another browser tab for later when we add the login form & password reset form.
Follow the instructions by installing the Userfront React package with:
Add the signup form to your home page by importing and initializing Userfront, and then updating the Home()
function to render the form.
Now the home page has your signup form. Try signing up a user.
Test mode
The form is in "Test mode" by default, which will create user records in a test environment you can view separately in your Userfront dashboard.
We'll continue by adding your login and password reset forms in the same way that you added your signup form. To allow a user to log out, we can call the built-in Userfront.logout
method.
Make the following updates in src/App.js
:
-
Update the
Login()
method to return the login form. -
Update the
PasswordReset()
method to return the password reset form. -
Update the
Dashboard()
method to display the user's data and a logout button.Note your
toolId
in your Toolkit for each form. ThetoolIds
in this code example will not work for your application.
At this point, your signup, login, and password reset should all be functional. Note that the login form on the /login
page will automatically redirect to /dashboard
if you are logged in.
Your users can now sign up, log in, log out, and reset their password.
Protected route in React
We don't want users to be able to view the dashboard unless they are logged in. This is known as protecting a route.
Whenever a user is not logged in but tries to visit /dashboard
, we can redirect them to the login screen.
We can accomplish this by wrapping the <DefaultLayout />
component in a <RequireAuth>
component that checks to see if the user is logged in. When a user is logged in, their access token is available as Userfront.tokens.accessToken
, so we check for this.
The RequireAuth
component uses Navigate
and useLocation
from React Router to redirect the browser if no access token is present.
Now, when a user is logged in, they can view the dashboard. If the user is not logged in, they will be redirected to the login page.
We now have a web application with signup, login, logout, password reset, and a protected route.
React authentication with an API
We saw above that the frontend has an access token available as Userfront.tokens.accessToken
when the user is logged in. This is a JWT access token that you can also use on your backend to protect your API endpoints.
There are many libraries to read and verify JWTs across various languages. The list to the right contains some popular libraries for handling JWTs.
Your React application can send the JWT access token as a Bearer
token inside the Authorization
header. For example:
To handle a request like this, your backend should read the JWT access token from the Authorization
header and verify that it is valid using the JWT public key found in your Userfront dashboard.
Here is an example of Node.js middleware to read and verify the JWT access token:
Using this approach, any invalid or missing tokens are rejected by your server. You can also reference the contents of the token later in the route handlers using the req.auth
object:
With this information, you can perform further checks as desired, or use the userId
or userUuid
to look up information related to the user.
For example, if you wanted to limit a route to admin users, you could check against the authorization
object from the verified access token:
React SSO (Single Sign On)
From here, you can add social identity providers like Google, Facebook, and LinkedIn to your React application, or business identity providers like Azure AD, Office365, and more.
To do this, create an application with the identity provider (e.g. Google), and then add those SSO credentials to the Userfront dashboard. The result is a modified sign on experience.
No additional code is needed to implement Single Sign On using this approach: you can add and remove providers without updating your forms or the way you handle JWT access tokens.
Final notes
Adding authentication and access control to your React application doesn't have to be a hassle. Both the setup step and, more importantly, the maintenance over time, are handled with modern platforms like Userfront.
JSON Web Tokens allow you to cleanly separate your auth token generation layer from the rest of your application, making it easier to reason about and more modular for future needs. This architecture also allows you to focus your efforts on your core application, where you are likely to create much more value for yourself or your clients.
For more details on adding auth to your React application, visit the Userfront guide, which covers everything from setting up your auth forms to API documentation, example repositories, working with different languages and frameworks, and more.