Using JWT in Your React+Redux App for Authorization

An explanation and guide for how to handle JWT and Authentication in the frontend of your web app

“How does Auth work with Redux and JWT ?”

The general idea is that you will save the current user’s information to the Redux store for easy access across your app. You will also save the JWT (JSON Web Token) associated with the user to localStorage so that their login can persist between sessions unless they explicitly logout.

This tutorial assumes you have the three following Rails API routes set up:

POST to /users

POST to /login

GET to /profile

These three routes handle the three essential parts of authentication — when a user creates an account, when a user logs into that account, and when a logged-in user revisits your web app. I will go in that order, although the final part (handling when a user revisits your app) is where JWT’s usefulness shines.

Let’s get started!

Note: This tutorial is mostly intended for Mod 5 students of Flatiron School’s Software Engineering Bootcamp who have implemented JWT Authentication to their Rails API backend but are unsure of how to have their client-side React+Redux App handle authentication. Hopefully, I can help clear up the workflow of Auth and how to apply it to your project!

1. User Signs Up (POST to /users)

When a new user visits your app, you may want them to sign up for an account. You will essentially run a standard POST request; you won’t have to do anything fancy to get this up and running.

If set up properly, your backend will create the user instance, salt the password using BCrypt, and then return an object with a user key and a token key. This object is the important part to Auth. You’ll see it later in this tutorial, but we will essentially take the user object and save it to your Redux store, then take the token associated with the user and save it to localStorage.

The steps for signing up new users and automatically logging them in are as follows.

Have a New User Submission Form

In your React App, you’ll want a form which, upon submission, will run your fetch in your actions.js file. You will be making use of Redux’s thunk here, so make sure you have it installed.

Create a controlled component that is a form for creating a new user. As an example, it may look like this:

Your component does not have to look exactly like this — the important part is the handleSubmit function.

Note where some unknown function named userPostFetch is being imported from actions.js and then added as a prop to the component using mapDispatchToProps . You can see above that this prop is invoked upon submission of the form. This will be the function that handles the fetch itself, as well as saving the user object to the Redux store and adding the token to localStorage. Next, we will write this function.

Have a function with the fetch request

In your actions.js file, it will look something like this:

Note the two separate functions: userPostFetch and loginUser . The function userPostFetch sends the user’s info to your backend to be verified. Upon success, it is expecting a response of a JSON object that looks like this:

{

user: {

username: "ImANewUser",

avatar: "https://robohash.org/imanewuser.png",

bio: "A new user to the app."

},

token: "aaaaaaa.bbbbbbbb.ccccccc"

}

In userPostFetch , this is what we named 'data' in the 2nd 'then' statement.

Save the token to localStorage

With the code we’ve written in our userPostFetch function, localStorage.setItem(“token”, data.token) will save the token ( “aaaaaaa.bbbbbbbb.ccccccc” ) to our user’s localStorage. This will be used later when we are persisting a user’s login between sessions.

To check that the token was saved successfully, run localStorage.token or localStorage.getItem(“token”) in your console.

Save user object to your Redux store

As for the user object, we see here that dispatch(loginUser(data.user)) is being ran. Presumably, your reducer will take the user object ( {username: “ImANewUser”} ) and save it to your Redux store. This will make it easy for any component in your React App to know who the current user is.

As an example, here is my reducer:

Here, the user object ( action.payload ) is being saved to the state under the key of currentUser. If you have Redux DevTools installed, you can check it after the successful creation of your user. You should see the user object.

You may notice here that I have a key of “reducer” that you might not have. This is because I have multiple reducers and named one of them “reducer”.

Note: You know how sometimes after you create an account on a website, it asks you to manually log in right after? You can have this for your website too — you don’t have to automatically sign users in after they create their accounts. If you decide to do this, simply do nothing with the user object and JWT token. In fact, you can edit the logic from your backend so that it only returns status messages instead.

That’s it for signing up a new user. Next, we’ll check out how to log in an existing user.

2. User Logs In (POST to /login)

Logging in a user is very similar to the signup process, except you are sending only the login credentials to the backend. The backend will handle validating the user and then sending back the same object from sign up — an object with a user key and token key. Once again, you’ll save the user object to the Redux store and save the token to localStorage.

If you have a component dedicated to logging in, it will look similar to your Signup component with one major difference — it will be importing a different function from your actions.js file. It might look something like this:

Note that the only thing that changed about the form itself was the removal of the avatar and bio input fields.

We haven’t yet written the userLoginFetch function, but again, its appearance is similar to the fetch that handled sign up. See below:

Note here that we are reusing the loginUser action in order to save our user object to our state.

Surprisingly, that’s it for logging a user in! When a user’s object is saved to the state and their token is saved to localStorage, you can consider your user logged in.

Now let’s do the third and final part: persisting your user’s login between sessions.

3. User Revisits (GET to /profile)

The point of saving a token to localStorage is to persist a login. When your user revisits your site, you want them to feel as if they are continuing their session from before.

Remember though that the token saved into localStorage is just a string. It in itself does not equal a logged-in user. You as the developer must take the token and translate it into a persisting login.

To do this, you will want to run your fetch (GET to /profile) every time your app is accessed if the user has a token saved into their localStorage. Running this logic in componentDidMount in your App component is a good choice, as it will definitely run when your app is accessed.

Your App component won’t look exactly like this (you can especially ignore the Switch and Routes parts), but the key part here is the getProfileFetch function being given as a prop to App and then invoked in componentDidMount.

We’re importing a function from actions.js called getProfileFetch , which is ran immediately when the App component mounts.

What getProfileFetch will do is run a standard GET request, except with an Authorization header with the token, which you handle in your actions.js file. Your backend should be set up to receive the token, decode it, and then return its associated user object. You then save this to the Redux store as usual. You already have the token saved to localStorage, so you don’t have to worry about it.

The function will look something like this:

The function getProfileFetch first checks if there is a token saved into localStorage before attempting to persist a login. This way, you won’t run an unnecessary fetch.

Note here again that we are reusing the loginUser action and that we are not re-saving the token. The user already has it in their localStorage, so there’s no need to save it again.

And there you have it — you have functioning authorization!