
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. 🤓💻