A Guide to Authentication in React

How I learned authentication in React

Ana
Level Up Coding

--

Introduction

I began this post with the intent to write a definitive guide on using Passport-Local in React because the resources out there were sparse on that specific topic. React is a library that builds user interfaces while authentication happens between the browser and the server (and a database in our case). So, lack of resources is due to the topics not being directly related.

To understand the topic more deeply, I built my own Authentication method using cookies and bcrypt. I was confused on how the different elements of a MERN (MySQL, Express, React, NodeJS) application interacted with each other in the context of authentication. I addition to covering registration, login, hashing passwords, and using cookies, I review:

  • How to make sure that CRUD takes into consideration the identity of the authenticated user so that they can only see, create, delete, update their own information.
  • How to make sure that only authenticated users have access to certain pages.

Welcome to A Guide on Authentication!

Below is a summary of my authentication approach:

Summary of my authentication approach

In React, information can be shared across components using Global State and Context. Either can be a valuable tool for determining what the user sees; however, we will depend on good ole fashioned cookies for authenticating users.

Step by Step on Authenticating Users in MERN app

Before we dive in, let’s clarify some definitions! Registration is essentially a request to create a new user record. Login is a request to be given access. Access is verified by checking if the user exists in the database and if the password provided matches the password in the record.

Think of registration as being added to the list to an exclusive club and login as showing up and having to show your id to prove that you are you.

First, we need to build a few paths

Start by creating the routes that the user will interact with directly — I chose to create two pages, one for login and one for registration or signup. In the App.js folder, I created the routes and referenced the pages I wanted to render.

Creating login and register routes in React

On the server side, I created one route /api/user to handle registration and login.

API calls on Server side to actually login and register user

The two pieces above can all be created on a single server file or split out into multiple files as I have done so. The concepts are still the same.

Registering A New User

When a user registers, they fill out a form with information such as name, username/email and password. For our application, we will use email as the primary identifier for our users.

…but should the password be stored in some special way?

I’m glad you thought of that! Yes. One way to do that is to use bcrypt, a library that hashes passwords. In plain speak, bcrypt maps the password into something unrecognizable. In fact, I created over 20 users with the password “password” and none of the outputted text was similar in any recognizable way. So, server side, before actually creating the User record in your database, hash the password. That way, if anyone hacks your database, they won’t be able to get any actual password information.

User Registration shown at the database level

An async function is used with bcrypt to ensure that the password is hashed before the User record is created. After creation, the User Id will be tied to a session record. Since the method is similar for registering and logging in, I’ll explain later.

Logging in An Existing User

When a user signs in, they fill out a form with their username/email and password. The values are bundled in the API request.

Server side, we are first checking whether the user exists and then checking if the password is valid. Note that the comparison isn’t a triple equal — The truth is that we can’t match them directly but the bcrypt compare function can.

User login shows at the database level

Great, we’ve technically authenticated a user! Now, we need to make it mean something:

Using Cookies to Restrict Access

Restricting Access based on authenticated User information

Let’s visit the database to understand how information interacts: User data (name, email, hashed password) is stored in a User Table. A second table stores User-Sessions by tracking Users and Active Session-Cookies relations. Every time we give a User a cookie, we store the cookie value and the User’s id in this User-Session table. Users have a one-to-many relationship with User-Sessions meaning that a single User could technically be authenticated on multiple browsers at the same time — think ipad, iphone and laptop.

How We Will Use Cookies

Now it’s cookie time! Cookies are a piece of data stored by a browser and sent along with every request.

When the user has been authenticated, a cookie will be created. Since cookies are included in every request, the server will be able to use Session Cookies to confirm the User’s identity. With every CRUD request, we will first check the Session Cookies, and use the User-Session table to match the cookie back to a User and returning the User’s identity if the relationship exists. Once the User signs out, the cookie is deleted and the User-Session record is deleted.

Illustration of how Cookies are set

Piraveena beautifully outlined relationship the between browser and server in this article. Check it out for your reference.

Creating a Cookie is the Easiest Part

It’s simple to create a cookie but it’s difficult to make it unique enough that it cannot be hacked. Welcome back bcrypt!

let cookievalue = bcrypt.hash("secretword",10)

To set the cookie, we just need to tie it to a response

res.cookie("cookiename", cookievalue)

In your app, you can check the application tab under Inspect to see if this worked correctly. Here is how you can tie session cookie creation to the login process:

You can take a similar approach for registration but rather than waiting to confirm that the password exists, you need to wait for the user to be created in the database. Grab the User Id from the response to create a User-Session record. Client side won’t receive a response until the User Session has been created.

Deleting Cookies is just as simple

When a user logs out, we remove the cookies in the browser and delete the User-Session relationship from our database.

Cookies are sent with every request. To access the cookie that you specifically set you can use a parser. I chose to parse the cookie myself to better understand how a parse may work. Note the user of decodeURIComponent to undo the URL encoding that happened when the cookie was set.

How to delete a user and parse out a cookie

CRUD is the tricky part

The most important thing we’ve learned about cookies is that they are appended to all requests. That means that on the client side, there aren’t additional pieces of information we need to append or provide in order to make API request.

The flow is as follows:

  • parse the cookies to get your specific cookie
  • use the value of the cookie to make a request to the User-Session table
  • if the result is null, or no relationship exists, return nothing
  • if there is a match, return the User Id and then use that id to fulfill the request that the user originally made

Every time the user makes a request, we are checking their credentials but doing it in a way so subtle that the client doesn’t know.

This method is effective because if the user logs out their authentication — the record on the user session table — will be wiped and they will be asked to log in again.

User Session Needs to Exist For User To Have Access

What About Context?

Since we are building a MERN app, Context is a tool available to share information across React components. Even if you chose to use User Context for tracking whether there is an authenticated user logged in, you still need to validate the user with cookies. Context will still be valuable to restrict what pages are viewable to the user at a given time. For example, set context that checks for cookies every time the page loads and if there is a cookie, you could show the sign out button because it means a user is logged in.

Since Context is on the Client side, we don’t want to depend on it as a method for confirming the user’s identity.

What’s Next?

Now that you have learned how authentication works, you can explore additional resources that teach you how to use Passport, indexedDB, cookie-parse, firebase-authentication, etc. The world is your oyster.

--

--

Guatemala -> San Bernardino -> Wellesley -> SF | Software Engineering Student | Passionate about people, puns, and data.