Using JWT in Your React+Redux App for Authorization

April 09, 2019 0 Comments

Using JWT in Your React+Redux App for Authorization

 

 

“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!

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 jwt 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.

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.

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."
},
jwt: "aaaaaaa.bbbbbbbb.ccccccc"
}

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

With the code we’ve written in our userPostFetch function, localStorage.setItem(“token”, data.jwt) 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.

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.

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 jwt 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.

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!

As you test your app, you’ll notice that currently the only way to log out is to clear the JWT token in your localStorage by typing localStorage.removeItem(“token”) into your console and pressing refresh to clear the Redux store. We should create a button for your clients to log themselves out.

Somewhere in your app, you’ll need a logout button which will do the above. While I wouldn’t place this randomly in the App.js file, here is an example of a logout button in App.js for sake of example.

You should notice a few new things: for one, there is a new action being imported called logoutUser, which we will write shortly. We also now have mapStateToProps being used in order for the App component to receive a prop called currentUser.

Note: Because I have multiple reducers, I had to access my Redux store’s current user by typing state.reducer.currentUser in mapStateToProps. If you only have one reducer, you will likely only have to write state.currentUser.

There is now also a ternary operator which checks if the currentUser prop received from the Redux store has a username key (as in, if the currentUser object is empty or not). If it does, it renders a ‘Log Out’ button, which will invoke logoutUser when clicked. It will also remove the token from localStorage.

logoutUser will just be a simple action:

This action will do the following in the reducer, replacing the currentUser with an empty object:

And there you have it! You should see the button appearing and disappearing when you log in and log out.

You may have followed the above and are questioning if your authorization is working. You can test it by signing up, logging in, pressing refresh, and logging out. If everything works as it should, you should see the user object saved to your Redux store under the key of currentUser using your Redux DevTools and a token saved to your localStorage which you can view in the console.

As for where to go from here, you can run checks in your components to essentially kick a user out if they are not logged in. You can do this with Redirect from react-router-dom or the push function from connected-react-router. These are the resources I prefer to use.

You can also now pass your currentUser object from your Redux store to any component in your app, as we saw earlier. For example, if you wanted your Navbar to show the user’s avatar, you could use mapStateToProps to pass the currentUser object to the Navbar and then render the avatar.

That’s all for now — Thanks for reading and happy coding!


Tag cloud