DEV Community

Cover image for Encryption, Authentication, and Authorization in Ruby on Rails
nickwarren47
nickwarren47

Posted on

Encryption, Authentication, and Authorization in Ruby on Rails

Have you ever been to a website and been denied access for not inputting the correct username or password? If not, well welcome back from North Korea because this web protection is seen unanimously across the internet. It is the first line of defense for a majority of web applications to prevent malicious users from uploading corrupted data, changing attributes on the site, or just helping to monitor traffic in and out of a web app. The question then becomes, how does this actually work in code? Well, we can look at the backend framework of Ruby on Rails to see how authorization capabilities are built into the code.

Description of Encryption
Before we see how Ruby on Rails allows us to authorize users in code, it would be helpful for us to first take a look at an encryption gem called "BCrypt". In Rails, a username and password are stored in a session hash in the database. This works just fine for functionality purposes but if a person is able to compromise the database, they will be able to see everyone's username and password and can use them for nefarious purposes. To prevent this, we need to encrypt the username and password so that an attacker won't be able to decode them and use them. To do this, we can create a hash function to handle our inputs and reduce them into indistinguishable code. For example:

def basic_hash(string)
  input.bytes.reduce(:+)
end
Enter fullscreen mode Exit fullscreen mode

In the code above, the hash function takes in a string and outputs the sum of the bytes from the string. This is a very basic means of encryption but has one flaw. What if we input a name of "Nick" and "Nhcl"? Both inputs would allow us to log in because the byte size of "h" is one less than "i" and the size of "l" is one higher than "k". Therefore, both inputs will work. Obviously, this posses a serious security risk as there are many string varieties we can use to get into the account. So what can we use to fix this? Well, Ruby has many gems (pre-programmed code segments) that we can use to add specific features to our code. One of these gems is BCrypt. BCrypt is a Ruby Gem what is a called a "cryptographic hash" meaning that it is designed to be incredibly difficult for an attacker to decipher the hash even with a given output in mind. For anyone who may be interested in learning more about the BCrypt Gem/would like to download it, there is great documentation of the gem here:

BCrypt Gem Open Source Documentation

The idea of BCrypt is that it will be extremely time consuming and labor intensive for an attacker to decipher the password and username hash. However, attackers are relentless and if they use thing called a "rainbow table." Basically a rainbow table is a password cracking method where hackers use a database of millions of leaked hash passwords to compare the hash produced by BCrypt (or other encryption software) to those in the table. This is a very labor and time intensive process but it can be successful in cracking the password from the hash. However, there is another method to add further complexity to the hashed password and that is a process called "salting." Salting adds a further layer of complexity to the hash by adding a "salt" which is a random string of code to the end of the password BEFORE it is hashed. Therefore, the rainbow table's effectiveness is greatly reduced as the salt provides more randomized code to the hash. BCrypt incorporates salting into its encryption, making it a very powerful tool for protecting passwords and sensitive data.

Authentication in Rails

Now that we've taken a small dive into encryption, let's see how we use it and other features to allow us to authenticate users in our Rails app. Authentication is the process of determining if someone is who they say they are. For example, if you go onto a website and enter a username and password in the signup page, you will thereby need to consistently provide the same username and password each time to verify that it is indeed you. With the constant threats of hackers compromising passwords, other authentication methods are being used such as 2-factor, biometric, CAPTCHA, and token authentications. For the purposes of this article, we will only focus on password authentication in Rails. To start authentication in Rails (in addition to uploading the BCrypt Rails gem), we need to first create some custom routes:

#in config/routes.rb 
  get "/me", to: "users#show"
  post "/signup", to: "users#create"
  post "/login", to: "sessions#create"
  delete "/logout", to: "sessions#destroy"

Enter fullscreen mode Exit fullscreen mode

These routes allow us to help the user to signup, login, logout, and to see the current user. You may have noticed that the login uses a POST action (to create the login) and logout incorporates a DELETE action. The reason for the create and delete actions is that the user, when logging on, creates a sessions hash which uses cookies to keep track of the interactions between the user and the server. Once the user logs out, the session hash is thereby destroyed along with the cookies that were used in the hash. We will need to define the actions in our various controllers (we will need a user_controller and a sessions_controller file).

Note: If you would like to user a Rails generator to produce the controller files mentioned above, you can run:
rails g controller Users --no-test-framework
rails g controller Sessions --no-test-framework

Let's take a look at first setting up the signup action in the user_controller.rb file:

# in app/controllers/users_controller.rb 
class UsersController < ApplicationController 

    def create
        user = User.create!(user_params)
        session[:user_id] = user.id
        render json: user, status: :created
    end

    def show
        user = User.find_by(id: params[:user_id])
        if user 
          render json: user, status: 202 
        else 
          render json: {errors: user.errors.full_messages}, 
          status: 422
        end
    end

    private

    def user_params 
        params.permit(:username, :name, :email, :password)
    end
Enter fullscreen mode Exit fullscreen mode

Note: status code: 202 = accepted, status code: 422 = unprocessable entity
In the code above, we first used the "create" action method to set up a user account that included the "username", "name", "email", and "password," (note: these were created using the "user_params" in the private method at the bottom. We additionally set up a "show" action method to allow a user to find their information based on their "user_id." Note: the "user_id" is not visible to the client but is used by Rails to find the user.

Now let's take a look at a the sessions_controller.rb file to see how a login/logout session are created for the user.

# in app/controllers/sessions_controller.rb 
class SessionsController < ApplicationController 

    def create 
        user = User.find_by(username: params[:username])
        if user&.authenticate(params[:password])
            session[:user_id] = user.id 
            render json: user, status: 201 
        else 
            render json: { error: "Invalid username or password" 
            }, status: 401 
        end 
    end 

    def destroy 
        session.delete :user_id 
        head :no_content 
    end 
end
Enter fullscreen mode Exit fullscreen mode

Note: status code: 201 = created, status code: 401 = unauthorized
In the code above, we set up the code so that if a user is authenticated, they will have access to the session that was created. If they are not authenticated, they will get an error message. Additionally, the "destroy" action method deletes the session based on the "user_id" and will return back a status of "no_content."

It may look simple but this code is extremely useful for creating the user authentication in our Rails app. Also, keep in mind, this is only for the backend of our app, the frontend also handles some of the logic for when a user is authenticated and what pages they can and can not access.

Authorization in Rails
Another feature we can add to our Rails app is authorization. Authorization is the process of giving certain users access to specific resources. Let's think of an example; let's say you are on a web app where there is a "premium" feature. The app developers want people to be able to see certain content for free so the user can determine whether or not they want to purchase the premium feature. And if you decide that you want to purchase the premium feature, the app developer must then authorize you to have access to the features in the premium section. Another example might be giving someone administrative access to an app so they can change specific code/features that the rest of the public shouldn't have access to.

Let's see how we will implement this in code. The first place to start coding for this will be in our application_controller.rb file. We will want to create a method that allows us to determine whether or not a user is authorized.

#in app/controllers/application_controller.rb
class ApplicationController < ActionController::API 

  before_action :authorize 

  private 

  def authorize 
    @current_user = User.find_by(id: session[:user_id])
    render json: {errors: ["Not authorized"]}, status: 
    :unauthorized unless @current_user
  end

Enter fullscreen mode Exit fullscreen mode

In the code above, you will notice that there is a "before_action :authorize" code. This code is telling our code to make sure the authorization method is run before performing any other action method. In the "authorize" method, the code is basically saying to find the "current_user" based on their ID in the session controller. If that ID is not found, then an error will be rendered unless the current user has been found. Now, you may be wondering, why are we handling the authorization in the application_controller.rb file and not the user_controller.rb or session_controller.rb files? If you look at the previous examples in the authentication section, you may notice how both include a "...<ApplicationController" in the class. This is telling Rails that the "user_controller" or "session_controller" classes will inherit code from the application_controller.rb file, thereby allowing those child controllers access to code we placed in the parent controller (in this case, the "application_controller"). This will be important for the next code we need to implement.

For each of our methods in both the sessions_controller.rb and the user_controller.rb files, we need to instruct our code which methods require authorization and which ones don't. For example, let's look at our sessions_controller.rb file:

# in app/controllers/sessions_controller.rb 
class SessionsController < ApplicationController 

    skip_before-action :authorize, only: :create

    def create 
        user = User.find_by(username: params[:username])
        if user&.authenticate(params[:password])
            session[:user_id] = user.id 
            render json: user, status: 201 
        else 
            render json: { error: "Invalid username or password" 
            }, status: 401 
        end 
    end 

    def destroy 
        session.delete :user_id 
        head :no_content 
    end 
end
Enter fullscreen mode Exit fullscreen mode

You will notice that we added this line of code: "skip_before-action :authorize, only: :create." This is instructing the code to not require the user to be authorized when they sign in. The user only needs to be authorized when they log out of the session. This is a useful application for determining what sections of the code our user can and cannot access without having the proper permissions.

That's it, we have successfully built out the authentication and authorization in the backend of our app. That being said, there is a setup for the front end but that is an article for another time.

Conclusion
The ability to authorize and authenticate a user is a very valuable tool for web app security. It will inevitably make your app much more user friendly and give you further control over the app.

Sources Links

Salting and Hashing
Ruby Documents on Hashing
Rainbow Tables Explained
Rails Authorization
Cover Photo
Rails Authentication
Rails Authentication
Rails Authentication and Authorization

Top comments (0)