Building the User Model and Session Controller for the Rails API Authentication App

1
1615

Now that our authentication app has been completely configured with our base settings, we can now start building out the authentication feature itself.

Creating the User Model

We’re gonna start by creating a User model. You can use any name for the model that you want, but the traditional approach is to use the name: User. To generate the User model, run the command:

rails g model User email password_digest

This will generate a migration file that looks like this:

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :email
      t.string :password_digest

      t.timestamps
    end
  end
end

You can see that we have two string columns along with the default timestamps. On a side note, after we connect our User model to Bcrypt, the password_digest will actually represent two columns:

  • A regular password field
  • A password confirmation field

Update the database schema by running:

rails db:migrate

From this point, we can customize the actual user model. The first piece of code to add is the has_secure_password method in the app/models/user.rb file.

class User < ApplicationRecord
  has_secure_password
end

This method is going to tell the user model that that password digest field needs to be encrypted.

Let’s also add a validation that ensures that every User has an email value and that every email is unique this will update the file to look like this:

class User < ApplicationRecord
  has_secure_password

  validates_presence_of :email
  validates_uniqueness_of :email
end

Let’s test this out by opening up the Rails Console with the command rails c in the terminal. From there we can run the code:

User.create!(email: "[email protected]", password: "asdfasdf", password_confirmation: "asdfasdf")

This showcases the magic of the password_digest field and the has_secure_password method. When we define that digest, Rails automatically knows that we want both a password and a password confirmation set of attributes in the user model, and that’s what it did for us. If you look at what gets returned when you run that command in the terminal, the other thing to note is that the password digest is no longer our ASDF that we gave it but instead, it is an encrypted set of strings, and this is tied directly to whatever the secret key is.

The way that encryption works and BCrypt works is it looks at our secret key. The application you generated has a different secret key than the application that I have. The process looks at that secret key, and it uses that as a salt. If you’ve never heard of a salt, it is a tool that an encryption algorithm uses to ensure or to hopefully ensure that the password or whatever it’s encrypting cannot be guessed.

Rails API Sessions Controller

So, now that we have a user model, now we can connect it to a controllers that will manage the data processes related to authentication.

The first step will be to create the route (also called drawing the route). Open your config/routes.rb file and add the following:

Rails.application.routes.draw do
  resources :sessions, only: [:create]
end

Now let’s create the corresponding controller that this route will point to, we can create a file app/controllers/sessions_controller.rb and add the following:

class SessionsController < ApplicationController
  def create
    user = User
            .find_by(email: params["user"]["email"])
            .try(:authenticate, params["user"]["password"])

    if user
      session[:user_id] = user.id
      render json: {
        status: :created,
        logged_in: true,
        user: user
      }
    else
      render json: { status: 401 }
    end
  end
end

Okay, so what’s going on here? If you’ve never built your own authentication, this may look a little weird. But what we’re doing is we’re we’re calling the User model and running a database query.

From there we’re performing a database query where we find the user by the email that is submitted. The params are what the front-end application is going to send.

As a preview from a future guide in this course, the user object that we will send from the React app to the API will look something like this:

{
  "user": {
    "email": "[email protected]",
    "password": "secret"
  }
}

So that is what the params code is doing in the controller, it’s grabbing the paramaters that will be sent from the front end app and it’s parsing them to retrieve the data. If you plan on sending data that is structured differently, you will need to adjust this code to match that structure.

Moving on, what does the try(:authenticate, params["user"]["password"]) code do? Because our user model is using the bcrypt gem, it automatically has access to the authenticate method. And we’re telling the system to check and see if the password that the user supplies matches the encrypted one that is stored in the database. Because all of our passwords are encrypted, we can’t simply run a query such as: User.find_by(email: params["user"]["email"], password: params["user"]["password"]). So the authenticatemethod does that for us.

If you’re an experienced Rails user and you’re curious on why I wanted to use a tool like Bcrypt as opposed to using a Gem like Devise, it’s because there’s actually quite a bit of functionality that Rails and more lightweight tools like Bcrypt provide by default that allows you to build your own authentication.

Continuing to walk through the code, if the system finds a user with the email that was provided, and the password matches, it will store the user object in the user variable. From that point, the method checks to see if the user variable has the database record or it is it nil.

If that conditional passes, we are storing the user’s id in the session with the code:

session[:user_id] = user.id

If your Rails’ session knowledge is a little fuzzy, this code is what stores the user’s ID inside of the encrypted cookie in the browser. This one line of code is the magic that will allow our Rails API to know which user is sending requests, such as forms, or accessing any page in our React application!

From that point, we are rendering JSON and sending a response back to the client, this will tell the React application that the process went through smoothly and that the user has been authenticated. I’m also returning the user object, a HTTP status code and a message so that the response is clearly defined and the React application will be able to kick off any processes that it needs to from there, such as redirecting the user.

So, now that we have the successful scenario taken care of, let’s also check to see what happens when the user was not able to log in successfully. This is a scenario where they may have forgot their email address, password, or they’re trying to hack into the system.

For all of those scenarios, we’re going to render JSON with a HTTP status code of 401. The 401 code is the HTTP that says that the request was unauthorized. This is the universal http code that you wanna use if a user is not authenticated.

And with all of that in place, we have a working sessions controller for our Rails API!

What’s next

In the next couple of guides, we’re going to build out the registration controller, the registration routes, and then we’re also gonna add a couple more pretty cool routes that allow the front-end application to check to see if a user is logged in on each page load.

Resources

1 COMMENT

LEAVE A REPLY

Please enter your comment!
Please enter your name here