Full-Stack Final Project Using React + Redux + Rails

For my coding boot camp final project I’ve made a full-stack web application utilizing React(client-side), Redux, and Ruby on Rails(server-side). The concept: ‘a safe space for the LGBTQ+ community to create and find social meetups near them’.

I'll be showing you how I set up my app; implementing user authentication with protected routes. This article is pretty long and I wrote it as I coded the application, I tried to be a thorough as possible but it’s a work in progress. Let’s dive in, starting with the Rails backend!

Github Project: https://github.com/boostinwrx/LGBTQ-meetup-app

Setup Backend

First, be sure to install the Bcrypt and JWT Ruby gems.

#gemfilegem 'bcrypt'
gem 'jwt'
gem 'pg'

We’ll be using JWT(JSON Web Tokens) for token-based authentication and bcrypt for password encryption. We also are utilizing PostgreSQL for backend database management.

Using the rails g command, we generate a user model with a username, password_digest(encrypted password), name, picture(url), bio, and preferred pronouns.

rails g model User username:string password_digest:string name:string bio:text pronouns:string 
Image for post
Image for post
# db/migrat/create_users.rb User Table

Be sure to insert the bcrypt method has_secure_password in your User model, followed by validations. For the sake of simplicity, we won’t touch on the model associations today (lines 3 & 4).

Image for post
Image for post
# /user.rb model

We’re going to set up three controllers: AuthController, ApplicationController, and UsersController.

Image for post
Image for post

Application controller begins with before_action :require_login , requiring the user to be logged in before anything else runs. encode_token utilizes JWT.encode to encrypt the token.

If we take a look at therequire_login action; it renders the JSON 'Please Login', status returns unauthorized unless the logged_in? action returns a valid, truthy,session_user.

session_user calls on decoded_token which:

  • Checks if the HTTP request has a header ‘Authorization’
  • Decodes the encrypted token using JWT.decode

In AuthController, you’ll see it begins with:

skip_before_action :require_login, only [:login, :auto_login]

Essentially, that allows login and auto_login actions to run without requiring the user to be logged in.

Image for post
Image for post

login action is fairly straightforward, it finds a user with a matching username .

If it finds a user with a matching username and bcyrpt authenticates that the username and password are correct (authenticate); it will return a JWT token for that user, effectively logging them in. If login fails (wrong password or username), it will return an error. The error has the key failiure and value:failure: “Log in failed! Username or password invalid!”

Now, let’s take a look at the create action in UsersController :

Image for post
Image for post
create action for User
Image for post
Image for post
Strong params

This action is also pretty straightforward, @user is set to the new user with the strong params,user_params. If the user params are valid, it encodes a token for that user, returning an encoded token to that user. It renders the JSON: {user: @user, jwt: token}. If the user is not valid, it’ll return an error user.errors.full_messages along with a status of :not_acceptable.

We’re almost ready to start working on the front-end, be sure to set the appropriate routes in config/routes.rb .

Image for post
Image for post

Frontend User Authentication With React + Redux

Looking at actionTypes.js, for now we’ll be dealing with the user actions exclusively; so ignore all the other actions.

Image for post
Image for post

Now this (below), is where the fetch calls take place, the user.js actions.

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

userLoginFetch

  • POST request to /api/login which routes to the login controller action
  • The body consists of username and password
  • if (data.failiure) — if the request returns a Failure, it will dispatch the failure error message to authError, it will return the error as the payload{type: AUTHENTICATION_ERROR, error: error }
  • If the login is a success, the token will be saved to localStorage — {
    localStorage.setItem(“token”, data.jwt)
    dispatch(loginUser(data.user))

loginUser

  • After authenticated login, the current user’s data is saved to currentUser in store

userPostFetch

  • POST request to /api/users calling on the UsersController create action on the backend
  • If successful, it create’s the user on the backend, giving it an ID
  • Finally, it returns the JWT token to localStorage, logging the user in, and setting it to currentUser
Image for post
Image for post

LOGIN_AUTHENTICATED

  • Called on from loginUser , takes user information as currentUser
  • authenticated: true, sets authenticated to true when a user is logged in
  • Also sets error to an empty string

AUTHENTICATION_ERROR

  • Sets error to whatever error message was received from fetch call

Let’s break down index.js

Image for post
Image for post
index.js BrowserRouter

The key to making protected routes function is the context via higher-order components. requiredAuth(componentName) is a protected component that only renders if authenticated, otherwise it will redirect the user to a page that gives them the option to log in or sign up. noRequireAuth(componentName) is not protected and can be accessed without logging in.

At the top of the component, localStorage.getItem(‘token’) is set to userToken .If userToken exists, the user is logged in, and AUTHENTICATED is dispatched, setting this.props.users.authenticated to true. This means the user will be automatically logged in upon their next visit.

In RequireAuth , the route is protected by routing the user to render the /loginRequired page, if they are not authorized. If they are authorized, the protected route’s component is rendered. Below are both the higher-order components:

RequireAuth.js

Image for post
Image for post

noRequireAuth.js

Image for post
Image for post
Image for post
Image for post

Home component is rendered above as LoginRequired component, requiring authentication to view any other page.

/loginRequired component prompts user to create account or login
/loginRequired component prompts user to create account or login
Image for post
Image for post
loginRequired Component

Login

Note: I won’t go in depth on the login or create account components but I’ll include them for your reference

If an error is returned, it will display as an alert, as seen below.

Image for post
Image for post
Authentication failed /login
Image for post
Image for post
Redux Action upon auth error
Image for post
Image for post

We can see that the LOGIN_AUTHENTICATED action has fired successfully.

Included for reference

Image for post
Image for post
Image for post
Image for post

Create Account

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Upon authentication, the token is saved to localStorage

We can see upon successful account creation, the user is logged in with the JWT token stored in localStorage.

A quick glance at the backend terminal confirms the successful user creation.

Image for post
Image for post
Image for post
Image for post

Included for reference

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

In Summary

Keep in mind, this is a work in progress, I haven’t completed it yet. I’m sure there are much more polished and secure ways to create a full-stack application. I hope you find parts of this long article, that I wrote while I coded, helpful.

Please feel free to email me with any questions, if anything needs clarification, or if you notice any errors I should correct.

Questions Are Welcome! Email Me: tifftiff.email@gmail.com

Written by

Full-stack developer. Alumna of Flatiron School's Software Engineering Immersive bootcamp. Portfolio: https://tiffany-codes.com/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store