Simple Fullstack CRUD App With Rails, React &Redux

Vanessa Fotso
6 min readNov 19, 2020

--

Working with a server-side language and a front-end framework to build persistent web applications can be very tricky. This guide will cover how to build a JSON API with Ruby on Rails and build a functional React and Redux front-end that interact with the API. We will be building a single page application, question and answer engine for web developer learners to post questions about the challenge they are facing while using HTML, CSS, JavaScript and Rails, and also provide insights and clarity by answering other users’ questions.

The app demonstrates basic user authentication with **JWT authentication** and **CRUD **(Create, Read, Update, Delete) functionality, as well as couple extra features like filter, flash message and reaction buttons. We will be working with three models: User, Question, and Answer. This guide is divided into three parts. Part 1 will cover implementing user registration, login and logout functionality in the server (Rails API) with JWT, part 2 will cover auth using JWT in the client side (React + Redux), and part 3 will cover the creating, reading , updating and deleting a question.

Part 1: User Authentication

We will be using a basic approach to implement JSON Web Tokens (JWT), setting up both our rails API and React to handle the generated tokens.

Rails API

The complete code for my rails API can be found here.

Run the following on your terminal to create your rails api:

rails new your_app_project_name --api --database=postgresql

We will need to add 2 essentials gems for our authentication gem 'bcrypt' and gem 'jwt' to our gem file. We will also add gem 'active_model_serializers' to render JSON with associated objects. You can learn more about active model serialization following the steps in this guide. Finally do not forget to uncomment rack-cors in the gemfile. This will make our AJAX requests possible. you can read more about it here.

Now you can go ahead and call bundle install on your terminal.

My complete Gemfile looks like this:

Next enable CORS in your app by uncommenting the following in config/initializers/cors.rb and changing the origins from example.com to * : mine looks like this

Now we need to create our User model, controller, routes, and create our data base. For that, run the following:

  • rails g model User username password_digest bio avatar this will also create the user table in the db
  • rails g controller api/v1/users
  • rails g serializer user
  • rails db:create * rails db:migrate

User Model

has_secure_password comes from ActiveModel and adds methods to set and authenticate against a BCrypt password. I also added some validation for user to have unique username with a minimum length of 4 and required the password to be at least 5 characters. As we can see in the private methode I am also converting to username to lower case before saving it in the database.

User Serializer

Again, this is an easy way to structure the data we want rails to return to our front side.

Notice we are omitting to return the password for security reason.

Routes

The app has the following routes for users:

User Registration / Sign Up

Now it’s time to implement authentication with JWT. Here is the flow for registration:

  1. a new user fill out the sign up form on the client side with valid username and password and submit the form
  2. a POST request is made to /api/v1/users which is redirected to the create action of the User Controller
  3. if an instance of user is successfully created, the server issues an encoded token to the client
  4. the client then save the token into the localestorage and presents it with every request to authenticate the user on the server side

Once the an instance of user is successfully created, a payload object or hash is created with the user’id and is encoded using the encode_token method. Since different controllers will need to authenticate and authorize users, it makes sense that our top level ApplicationController handles the functionality of encoding/decoding tokens so that it can pass it down to all controllers.

This is how the method looks like in the ApplicationController

the JWT gem provides the encode method which we use to generate the token. This method takes up to three arguments: a payload to encode, an application secret (of your choice), and an optional third that can be used to specify the hashing algorithm used. The return value is a JWT as a string having the following structure

"eyJhbGciOiJIUzI1NiJ9.eyJiZWVmIjoic3RlYWsifQ._IBTHTLGX35ZJWTCcY30tLmwU9arwdpNVxtVU0NpAuI"

upon successful registration, the server renders the user object and the JWT (key/value pairs) as a JSON object. If registration failed, the server renders the list of errors as a JSON object.

This is a fetch example for signing up:

User Login

logging in follows a similar approach as signing up, except that the user does not need to be recreated, but must authenticate with valid credentials. This is handled by the login action of the Auth Controller.

in **app/controllers/api/v1/auth_controller.rb

Here, the user send its credentials (username and password) to the server, then the server lookup the user by username using the active record find_by method. If there's a username match in the database, the server then compares the provided password to the one stored in the database using the authenticate method provided by bcrypt.. If the user is successfully authenticated, JWT generates a token which is rendered to the client side as well as the user instance in a JSON format.

Auto Login (User Revisiting the App)

When the users revisit the app, we will them to continue their session from before if they have a token saved in their locale storage ( the whole point of saving the generated token). Because when using tokens the server does not save sessions, thus does not remember the current user, the client will need to provide the token saved in the locale storage for the server to lookup and render the current user.

Referring to the JWT documentation: Whenever the user wants to access a protected route or resource, the user agent (browser in our case) should send the JWT, typically in the Authorization header using the Bearer schema. The content of the header should look like the following: Authorization: Bearer <token>

Let examine the code for auto login:

In app/controllers/api/v1/auth_controller.rb

auto login seems pretty straight forward. It renders the current user. However, the auto_login action cannot be accessed unless the method require_login allows it. This pattern is enforced by the macro before_action that instructs the API to run the require_login method before any action, unless skipped ( skip_before_action :require_login, only: [:login]) like it is the case for the login action. It will not make sense to require the user to be logged in before logging in.

Let explore our current_user and require_login methods in the Application Controller, as well as all the jwt functionalities. Below is the final Application Controller

The require_login method returns an error message 'Please Login or Sign up to see content' unless the user is logged in. The logged_in method check if there is a current user, which is obtained by decoding the token provided by the client when sending the request . The server uses the decode method provided by JWT gem to decode the token and obtain the user_id that is used to look up the current user.

The JWT.decode takes three arguments: a JWT as a string, an application secret, and--optionally--a hashing algorithm. We set up our server (by defining the auth_header) to anticipate a JWT sent along in request headers, instead of passing the token directly to ApplicationController#decoded_token

Normally, if the server receives and attempts to decode an invalid token, the decode method will raise an exception causing a 500 Internal Server Error. The Begin/Rescue syntax prevents us from crashing the server, allowing us to rescue out of an exception in Ruby. Thus, if the server receives an invalid token, the decoded_token will simply returns nil.

The header obtained from the fetch is converted into an array with the first element being the Bearer and the second element the jwt-token. Only the jwt-token is needed here to decode into the user_id.

Again note the before_action :require_login set in the application controller which automatically lock down the application unless the client provides a valid token or the before action is set to be skipped for a specific action.

This is it for the basic implementation of JWT in a Rails API. Please read more about JWT and other approaches here.

We will be covering JWT implementation in our client-side ( React & Redux) in my next post.

Until then, Happy Coding!!

Originally published at https://vanessuniq.github.io on November 19, 2020.

--

--

Vanessa Fotso
Vanessa Fotso

Written by Vanessa Fotso

Health IT Software Engineer with broad technical exposure and passion for learning.

No responses yet