The Frontend of JWT Auth (part II)

In my last blog, I covered setting up JWT authentication in Rails (server side). This post will cover the client side in React.

Note: I will be using localStorage-based authentication, not session storage. That means that I will be storing my JSON Web Tokens (JWTs) in the browser’s localStorage (a type of web storage, where you can store and access data directly in your browser; it also does NOT expire after you close the browser window, which is convenient for staying logged in).

Your Backend Is All Set Up! Now What?

If you’ve successfully integrated JWT authentication in your Rails API, it’s time to think about how you will develop the frontend functionality.

Another note: For easier reading, I will only include code snippets from my app related to authentication. If you’re interested in seeing the rest of my code, view the repo here.

Components

In addition to App.js, I created the following components: SignUp.js, Login.js, Logout.js, and Profile.js.

In my App.js file, I set up state for a username and loggedIn status. I also created a stayLoggedIn function, which would check localStorage for an existing user object as soon as the app fires up. If there was no user object (!user), then it would return. If it did find one, then it would update state. I invoke this function on componentDidMount(), so if loggedIn is updated to true as a result, I then route the user to /profile (see line 44).

I pass down the remaining functions (updateUsername and toggleLoggedIn) as props to the other components. We only pass down clearUser to the Logout component.

SignUp Form

Time to set up a controlled form for your user to fill out if they want to create an account. There were 3 main things I wanted to accomplish in this component: manage state, send a POST request to create a new user, and display an error from the server if signup failed.

My handleSubmit function sends a POST request to my users route on the backend with a user object containing the username and password inputs from the signup form. If the fetch request does not return data with a JWT, then we update state for the displayError to show the error from the server. Remember this error from the create action in our users_controller?

def create...elserender json: { error: 'That username already exists. Try again.' }, status: :not_acceptableend

Also recall from the last blog that our User model validates for a unique username, so we would cause a signup error by trying to create a new user with an already existing username.

class User < ApplicationRecordhas_secure_passwordvalidates :username, uniqueness: {case_sensitive: true}end

We can use a ternary operator (line 64 in the SignUp.js snippet) to get the error to display on the form. Add a little CSS to make the letters red, and you get this:

Back to our handleSubmit function. So, if our POST request is successful, then we update the username on App.js by invoking the props function updateUsername and passing in the username from the data we just received (line 34). Then, we clear localStorage and set a new item in localStorage with the userInfo we create from data. Last, we update the loggedIn state from false to true using thetoggleLoggedIn props function.

If all goes well, we can route our user to their profile page by adding some logic to the end of our signup form:

{this.props.loggedIn ? <Redirect to='/profile' /> : null}

Login Form

The logic for login is almost identical to SignUp.js, except for the fetch URL:

fetch('http://localhost:3000/api/v1/login', {
...

If the login fails, the error from the server side reads:

elserender json: { error: 'Incorrect username or password' }, status: :unauthorizedend

So, if we try to log in with an incorrectly spelled existing user or we try to log in with a username that doesn’t exist, we will see the error above pulled from the server.

If login is successful, we will be routed to the user’s profile just like in signup.

Logout

Now that signup and login work, we can set up logout functionality. Recall that we created a clearUser function, which we passed down as a prop to the Logout component. The function clears localStorage, sets state of the username to an empty string (“ ”), and toggles loggedIn.

So, in our Logout component, we can invoke the clearUser function, and since that will also toggle loggedIn, we can use a ternary to conditionally redirect our users back to the login form.

I hope this tutorial was helpful. Please leave a comment if you have any suggestions for how I could improve my code. I always appreciate feedback from other coders. 🤓💻

Full Stack Software Engineer open to work. Experience in JavaScript, React.js, and Ruby on Rails.