User Authentication in React via Rails API

A comprehensive guide to User authentication for Rails/React

John Guest
The Startup
11 min readJun 21, 2020

--

So you have decided that your new project is going to use a Rails API for the server-side and React for the client-side. You enter rails new and create-react-app and get to work. You decide that you want to implement user authentication. Aside from a few configuration changes to allow the use of cookies (which I will explain very soon), where to start?

In this article, I will walk through the needed steps and demonstrate how this is very straightforward and simple. I will be using Rails 5 but everything should work for Rails 6 as well.

Phase 1: Configuring Rails App for Cookies

If you did not use the --api flag in the rails new command the following steps should not be necessary. If you are starting from an existing Rails API there are a few configuration changes that I will cover in this section.

The gems you will need are bcrypt and rack-cors . The gem ‘bcrypt-ruby’ is a Ruby binding for the OpenBSD bcrypt() password hashing algorithm, allowing you to easily store a secure hash of your user passwords. The latter is middleware for handling Cross-Origin Resource Sharing (CORS), which makes cross-origin AJAX possible. It will allow you to whitelist the origin of requests to the Rails server.

Place those in Gemfile and run bundle.

Let’s start with “application_controller.rb”. You will see the following.

The API Controller is a lightweight version of ActionController::Base, created for applications that don’t require all functionalities that a complete Rails controller provides, allowing you to create controllers with just the features that you need for API only applications. So to get the complete functionality of a Rails controller, we will change this line.

Next, we will move to “config/application.rb” where we will apply middleware that will allow us to send receive cookies. Add these two lines.

We will also change the line that statesconfig.api_only = true to config.api_only = false. It should be around line 37.

You will now need to create two files at “config/initializers/cookie_serializer.rb” and “config/initializers/session_store.rb” . This will configure how our server will handle the HTTP requests. The server needs to know the key and the domain.

By convention, the key name is your application’s name and should start with an underscore.

The next file you will need to create is also in “config/initializers”. In that folder create a file called “cors.rb” and insert the following.

By default, your React app will run on “http://localhost:3000”. The above code gives all requests from that address access to all server resources and allows them to utilize all HTTP methods.

Another important change is in “config/puma.rb”. We will change the default Rails server from 3000 to 3001 to prevent the server and client from attempting to run on the same server.

And that’s it for our config changes. You should be set up to start building the controllers!

Phase 2: The User Model and Sessions Controller

The easiest way to get a user model going and using bcrypt for secure passwords is with rails generate.

The careful observer will note that we are creating a password_digest column instead of “password”. This is what is required by the bcrypt gem as the column in the user table in which to store the encrypted password. Thanks to the magic of encryption, the user model will never see the actual password and our database will never store the password but it will store a “hashed” and “salted” version of the password called a password_digest.

We can now create and migrate our database.

After migration, it is best practice to check “db/schema.rb” to make sure that everything was set up correctly. The user’s table section of the file should look something like this.

To use the secure password functions of bcrypt will need to add a line in “user.rb”. This is also where we can use a little ActiveRecord help and add some validations to the model.

Now we can draw start to plan out what kind of actions a user will need. Of course, a user will need to create a username and password and will need to see there own data. We can take care of those needs with just two routesshow and create. We will add a third route so we have access to an action that will correspond to a list of all users and as per convention, this route will be named index. Here is the “config/routes.rb” so far.

The file can also look like the following if, to quote The Dude, “you’re not into the whole brevity thing”.

Now we can connect the user’s controller. We will build actions that correspond to each of the routes. I will be using standard “REST-ful” routing and assuming that you are familiar. You will need to create a file “app/controllers/users_controller.rb”.

In the create action I am using a helper method login. We will define this method and others in the next step. The natural home for helper methods is the “parent” of the UsersController, ApplicationController.

A few things to note here. At the top we haveskip_before_action :verify_authenticity_token . This is a security token generated by Rails to prevent cross-site request forgery (CSRF). We disable this to prevent “forbidden” parameters that would cause errors in our controller actions. We also have helper_method :login!, :logged_in?, :current_user, :authorized_user?, :logout!. This allows for these methods to be passed to all other controllers in the app. We will shortly be using user data to create “sessions”. The sessions will be created and managed in the SessionsController. First, we should draw the sessions routes. We will need login and logout and we will add a GET route for /logged_in that will tell us the login status and the current logged in user.

Now, for the controller.

We are authenticating the user data with .authenticate, a method that the user model gets from bcrypt and the has_secure_password line in the user class.

Phase 3: The React Client

Again, I am assuming that you have a functioning React app that you will be working with and basic knowledge of React. To best follow along, your React app should be placed in a directory “/client” that is in the top level of the Rails app. We can now install some dependencies that you will need. We will be using Axios for the calls to the API and react-router for client-side routing.

The main container and acting router of the client app will be “App.js”. Here, we will import dependencies, convert App into a functional component into a class component as well as write a mock-up of our routes. We are using which renders component exclusively as opposed to inclusively meaning each component is rendered on its own without appearing inside of another component.

We will need two methods that will handle the state of the component. They will update state with the status isLoggedIn and user data that will be returned from the API. The next post on my Medium will involve the use of redux and thunk to manage and dispatch actions to a reducer that we will build to handle much of the login logic. For now, we will use the state of the App component.

These two methods will update state with the current session data so user data can be made available only to the logged-in user. How do we get the session data? Obviously, we will make aGET request to our /logged_in route that will send us the status and the user but where is the best place to do this? Well, we want it to updated every time that the App renders so naturally, we use componentDidMount(). First let’s make the request to the backend.

Here we use Axios to send a GET request to our /logged_in route making sure to add {withCredentials: true}. With this credentials object the request will not work. You can see here one of the advantages of using Axios over fetch(). In a fetch() request, the object that is returned has to be converted to JSON using the .json() function. Axios converts the response to JSON automatically. Now we can add this function to componentDidMount().

Our App component should now look like this.

Phase 5: Home, Login, and Signup Components

We now have a solid backbone of a front end user authentication system. What we are missing now is a place for the user to log in and sign up. First, we will start with the Home component which will act as our landing screen. It will be stateless and functional.

Home is just a place to provide links to our other components usingLink imported from react-router-dom. The Login and Signup components will be almost identical and could be made into one however, we will not go into how to combine the elements into one in the interest of staying focused on our main goal. They will be using basic controlled forms and Signup will have an added attribute of password_confirmation (using snake_case as opposed to camelCase because this key will be read by Ruby and we like consistency).

Login:

Signup:

Let’s break these components down a bit. The forms are what we call controlled forms. This means that the state of the component is updated vie onChange() so that when the form is complete all of the data in the form is in our state. We then utilize onSubmit() to call our handleSubmit() functions.

We can now use axios.post which will need an object that will be sent to the specified route. Here is handleSubmit() in Login.

We have also added handleErrors() that will render our errors from the .catch() function in our request.

We use our user object from state to post to the desired route remembering to include {withCredentials:true}. We have also included a method for redirecting after handleSubmit()is complete. We have access to the history props passed to us from react-router-dom when we use render=props on App.js.

We now have a user authentication system for our React client! How you utilize it is up to you. You will have access to isLoggedIn and can pass it as props wherever you need it to prevent or allow users to have access to components and data within your app.

--

--

John Guest
The Startup

“The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.” — Tim Berners-Lee