Full Auth Walkthrough Using Rails

Lucas Thinnes
5 min readSep 28, 2021

--

In this article I am going to explain how to get backend user authentication working. My intent is to blaze through this and make it as easy as possible to get the reps down in order, so if you are looking for a detailed breakdown of each step, you may want to look elsewhere! For those just trying to get the process under their belt, read on-

INITIALIZATION

Assuming you have Rails installed, create a new Rails app using the following commands:

rails new auth --api -T

Once it is installed, navigate to your gemfile and uncomment the line that says this (don’t worry if the last three numbers aren’t the same):

gem 'bcrypt', '~> 3.1.7'

We will also need the JWT gem, so add this to the gemfile as well:

gem 'jwt'

Save the file now run this command from the directory folder:

bundle install

This will install all of the gems from the gemfile, including bcrypt.

CREATING THE USER AND PASSWORD CLASS

Next we have to create the user class, this looks as such:

rails g model User username password_digest

“password_digest” as an entry simply allows bcrpyt to access the password parameter.

Next, we want to migrate this:

rails db:migrate

Now, navigate to config/routes.rb and add this as a route:

resources :users, only: [:index, :create]

Next, modify the app/models/user.rb to look like this:

class User < ApplicationRecord
has_secure_password
end

Then, head over to app/controllers and create a file called “users_controller.rb” and create an index method that inherits from ApplicationController as such:

class UsersController < ApplicationController  def index    @users = User.all
render json: @users
endend

We can then run “rails s” from our terminal to make sure everything starts alright. Checking the index method via http://localhost:3000 using Postman should return an empty array:

Next we are going to add a create method for the user class in user_controller.rb using strong params, this looks as such (following our index method):

def create
@user = User.create(user_params)
end
privatedef user_params
params.require(:user).permit(:username, :password)
end

We can now create users in Postman:

That was a lot of work, but we have user creation and password encryption under our belt. Go take a break and fuel up.

(this article is sponsored by 7eleven and mescaline.)

Next up we have got to make a route for user login. Head over to routes.rb and add this beneath the resources:

post '/login', to: 'authentication#login'

Now head over to app/controllers and add a file called “authentication_controller.rb”. We will want to initialize the class and inherit from ApplicationController. The goal is to create a login method which finds by the parameter of the username and validates based on the password. Upon validation, we should obtain a JWT token. In Postman, we should be able to test this by returning the JWT token. The file will end up looking like this:

class AuthenticationController < ApplicationController  def login
@user = User.find_by(username: params[:username])
if @user if @user.authenticate(params[:password]) payload = { user_id: @user.id } secret = Rails.application.secrets.secret_key_base token = JWT.encode(payload, secret) render json: { token: token } else
render json: 'Login failed.', status: :unauthorized
end
else
render json: 'Login failed.', status: :unauthorized
end
endend

I will explain this one a bit because it is a substantial process:

  • Login is defined as a method. “@user” is found by its parameter.
  • If the user exists & the password contained within the request matches, the payload is set to the user ID and the secret as the key base within the application (for the purpose of uniquely distinguishing this parameter).
  • The JWT is declared as “token” and encoded with the two previous parameters (more can be read upon this process on the JWT site).
  • The token is rendered.
  • “Else” cases are created for either a failed password or missing username, yet left ambiguous to ensure security.

Sending a POST request to the “login” route with an existing user should now look like this:

(notice a period separates the payload & secret in the token.)

Now we have to decode the token, the final step in the authentication process. This is another large chunk of code that I will explain after posting. Head over to app/controllers/application_controller.rb and modify it to look as below:

class ApplicationController < ActionController::APIdef authenticate
begin
authorization_header = request.headers['Authorization']
token = authorization_header.split(' ')
secret = Rails.application.secrets.secret_key_base
decoded_token = JWT.decode(token, secret)
rescue
render json: { message: 'Login failed.' }
end
end
end

Here’s what’s going on:

  • A begin/rescue block is formed, this is very similar to a try/catch block in JavaScript, which is essentially capturing errors like an if/else.
  • We store the authorization header in a variable. The header comes from the request and contains the data of the string “Bearer” then the token we retrieved above.
  • The token is obtained from this header by splitting it at the space and grabbing the second item in the new array.
  • The secret is the same as in our authentication controller. These need to be exact matches for the authentication to occur.
  • The decoded token is set just as the encoded token, but the token is the first argument instead of the payload.
  • If the request fails, it will let you know from the rescue portion.

Now, we add a “before_action” to the users controller. Before the index method, add this:

before_action :authenticate, only: [:index]

This will run the authenticate method before giving you all of the users. This is possible because the users controller inherits from the application controller, so any methods you write in there will be accessible to the child classes! Cool, right?

We now have working auth. I may write on connecting this to the front end next! Thanks for sticking with me.

--

--

Lucas Thinnes
Lucas Thinnes

Written by Lucas Thinnes

Programmer / Artist / Believer

No responses yet