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: "r@dev.com", 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 template: 'users/show'
else
if @user == nil
flash[:alert] = "Username not found."
else
flash[:alert] = "Incorrect password."
end
redirect_to home_path
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@example.com",
"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 authenticate
method 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 show and sending a response back to the client, this will tell the application that the process went through smoothly and that the user has been authenticated. I’m also returning the user object..
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 home with an error message that says that the request was unauthorized. This is the universal flash message 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 App SECURE!
Top comments (0)