Skip to main content

Authentication in Web Apps

Updated Aug 24, 2023 ·

Overview

Web applications need to know who is using the system, so users must sign up and log in.

  • Users enter a username and password
  • Applications verify credentials
  • Access is granted or denied

In the past, passwords were stored directly in databases, which caused serious security risks.

  • Databases can be compromised
  • Plain text passwords are exposed
  • User accounts become vulnerable

To keep users safe, modern applications always store passwords in a protected form, not as the original text.

Password Hashing

Instead of storing passwords directly, applications store a transformed version of them.

Bcrypt is commonly used in Ruby applications to handle password hashing securely.

  • Adds randomness using salt
  • Produces different hashes for the same password
  • Supports secure comparisons

This makes it much harder for attackers to guess or reuse stolen password data.

Reference: bcrypt

Installing Bcrypt

Before using Bcrypt, it needs to be installed and required in your project.

## main.rb
require 'bundler/inline'

gemfile true do
source 'http://rubygems.org'
gem 'bcrypt'
end

To run the script:

ruby /path/to/main.rb 

Output:

Fetching gem metadata from http://rubygems.org/.
Resolving dependencies...
Fetching bcrypt 3.1.20
Installing bcrypt 3.1.20 with native extensions

Once installed, you can hash passwords and verify them safely.

Reference: How to use bcrypt-ruby in general

require 'bcrypt'

my_password = BCrypt::Password.create("my password")
#=> "$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa"

puts
puts my_password
puts my_password.version #=> "2a"
puts my_password.cost #=> 10
puts my_password == "my password" #=> true
puts my_password == "not my password" #=> false

Expected output:

Fetching gem metadata from http://rubygems.org/.
Resolving dependencies...

$2a$12$S73P725rXI6iWknccFgzuuafqzr9LlS7fIvimVBA1vY3dNeQD6eFK
2a
12
true
false

This ensures passwords are never stored in plain text and can be safely verified.

Using Bcrypt

Bcrypt allows you to create hashed passwords and verify them against user input.

  • Hash a password using BCrypt::Password.create
  • Compare a stored hash with the original password
  • Each hash is unique due to salting, but comparison still works

Example:

require 'bundler/inline' 

gemfile true do
source 'http://rubygems.org'
gem 'bcrypt'
end

require 'bcrypt'

my_password = BCrypt::Password.create("@Thr3@tL3v3Lm!dN!8hT")
puts my_password
puts

This returns the hashed of the password "@Thr3@tL3v3Lm!dN!8hT":

$2a$12$WQw1LIO3alJ/6ZF8/BbgB.j99kxLLuDF6nPjKoMlaNDIyUbpVArtm 

You can then verify the password using the stored hash by plugging it to the new() method for Bcrypt:

my_password = BCrypt::Password.new("$2a$12$WQw1LIO3alJ/6ZF8/BbgB.j99kxLLuDF6nPjKoMlaNDIyUbpVArtm")
puts my_password == "@Thr3@tL3v3Lm!dN!8hT"

Output:

true 

As we can see, it returned true, which confirms that the input password matches the hashed password.

Creating Secure Passwords

You can create methods to handle hashing and verifying passwords for multiple users.

  • Create a method to hash a password
  • Create a method to verify a hashed password
  • Create a method to convert all user passwords to hashed versions

Note: If you already installed the gem using bundler/inline or through the gem utility, you don’t need to repeat it anymore.

require 'bundler/inline' 

gemfile true do
source 'http://rubygems.org'
gem 'bcrypt'
end

You can directly require Bcrypt in your Ruby files

## crud.rb

require 'bcrypt'

## USERS---------------------------------------------------------

users = [
{ username: "adam", password: "adam123" },
{ username: "alex", password: "alex123" },
{ username: "bob", password: "bob123" },
{ username: "james", password: "james123" },
{ username: "john", password: "john123" },
{ username: "robin", password: "robin123" },
{ username: "ted", password: "ted123" }
]

## FUNCTIONS-----------------------------------------------------

def create_hash_digest(password)
BCrypt::Password.create(password)
end

def verify_hash_digest(password)
BCrypt::Password.new(password)
end

# Convert passwords to hashes
def create_secure_users(list_of_users)
list_of_users.each do |user_record|
user_record[:password] = create_hash_digest(user_record[:password])
end

list_of_users
end

puts create_secure_users(users)

Run the script:

ruby crud.rb 

Expected output:

{username: "adam", password: "$2a$12$AvFiVn.lH7gLHtVkVAEazuLI0RA9r2/PnA2SJAXzbwe39UGUiy0TK"}
{username: "alex", password: "$2a$12$gbBQv43.DBs6cJ45Z1yne.DIsVaFFy4L7bfNS5O7H/KMIi8O5apfW"}
{username: "bob", password: "$2a$12$N7tPW3XelbZ9A22oTNDbHuX5R3ZiEgtdSWjWpMXuZNsdHI11Jl86a"}
{username: "james", password: "$2a$12$NWLHyg6j84bikz2VyoM5i.W7Vhw9Oyg/IMOrkI4XMdFVokVLuyGn6"}
{username: "john", password: "$2a$12$lcqreJnA1xWv3PMwmUSUGewyHnFXISVuJF7JIwgPpoc5.FtQfYIqq"}
{username: "robin", password: "$2a$12$S/uo2gMbcO5sPkODsuR6led24ntmtrp5h8gKpXjjDyczo1paegsHq"}
{username: "ted", password: "$2a$12$f5Sf5Zn2Fkvea5wHhEdNB.1epoZSdUks8MjiG4v5RhBcWveXnZQl."}

This approach ensures all user passwords are securely hashed and can be verified safely without ever storing plain text.

Using Modules

You can group these methods into a module to reuse across different classes.

  • Modules act as toolkits for classes
  • Include a module in a class to give it new methods

In the example below, the Crud module is created to hold multiple methods for handling passwords, including hashing, verification, and user authentication.

info

Notice that the method names are all prefixed with Crud. This makes them accessible directly through the module without needing to include it in a class.

## crud.rb 

module Crud
require 'bcrypt'
puts "Crud module used"

def Crud.create_hash_digest(password)
BCrypt::Password.create(password)
end

def Crud.verify_hash_digest(password)
BCrypt::Password.new(password)
end

def Crud.create_secure_users(list_of_users)
list_of_users.each do |user_record|
user_record[:password] = create_hash_digest(user_record[:password])
end

list_of_users
end

def Crud.authenticate_user(username, password, list_of_users)
list_of_users.each do |user_record|
if user_record[:username] == username && verify_hash_digest(user_record[:password]) == password
return user_record
end
end

puts "Incorrect credentials."
end
end

You can load the module in another file using:

  • require_relative 'crud' - If both files are in the same directory
  • require 'crud' - If it’s installed as a gem or in the load path
  • $LOAD_PATH << "." - To add the current directory to Ruby’s load path

In this example, both main.rb and crud.rb are in the same directory:

003-Practice-03/
├── main.rb
└── crud.rb

The main.rb can use the Crud module to hash user passwords:

## main.rb 

require_relative 'crud'

users = [
{ username: "adam", password: "adam123" },
{ username: "alex", password: "alex123" },
{ username: "bob", password: "bob123" },
{ username: "james", password: "james123" },
{ username: "john", password: "john123" },
{ username: "robin", password: "robin123" },
{ username: "ted", password: "ted123" }
]

hashed_users = Crud.create_secure_users(users)
puts hashed_users

Running the script:

ruby main.rb

Output:

Crud module used
{username: "adam", password: "$2a$12$UbQ6bVjFF5AtphY08eMKpOk2d79r6FjLxNxJfBDWy4jjN.PaQsWMK"}
{username: "alex", password: "$2a$12$PQA5pHlI9fGH5jiwJ5XrguiN2gvJb2XGw7/kBTVNS2qXmRze3ntY."}
{username: "bob", password: "$2a$12$.3CjelhDXP9QiGwv.0Qh.ON3cpaQR5XlvVH8cKA1/fRPr/40dSwyG"}
{username: "james", password: "$2a$12$lvTQPD1hn7HQLfaJW4mFmO1nB4Xqgop79q7JpZK6XkRBQFDdc1pJC"}
{username: "john", password: "$2a$12$wb0bAjc8BGhWHmMQEJEZxeFDvZ7bLLaNmyubNsD0X7qExMm7Pe6QG"}
{username: "robin", password: "$2a$12$o5XpNQjCZy/Zy.tFkeaSseqwab9Jh8MNlqUATiH9E2qHrV9Qj0aom"}
{username: "ted", password: "$2a$12$g7Lxzf2v2LJ5DiQwS7N31OA3cGaKAJjtWAYQMbW3sIIIzm/34EFTq"}

Class Method

You can define module methods using self. notation. These are class methods, which means you can call them directly on the module without creating an object.

  • Prefix method names with self. inside the module
  • Methods can be called using ModuleName.method_name
  • Useful for utility methods that don’t need an object instance

Example:

## crud.rb 

module Crud
require 'bcrypt'
puts "Crud module used"

def self.create_hash_digest(password)
BCrypt::Password.create(password)
end

def self.verify_hash_digest(password)
BCrypt::Password.new(password)
end

def self.create_secure_users(list_of_users)
list_of_users.each do |user_record|
user_record[:password] = create_hash_digest(user_record[:password])
end

list_of_users
end

def self.authenticate_user(username, password, list_of_users)
list_of_users.each do |user_record|
if user_record[:username] == username && verify_hash_digest(user_record[:password]) == password
return user_record
end
end

puts "Incorrect credentials."
end
end

You can call the methods directly from main.rb using the module name as a prefix. For example, to use create_secure_users, call it with Crud.create_secure_users(...):

## main.rb

require_relative 'crud'

users = [
{ username: "adam", password: "adam123" },
{ username: "alex", password: "alex123" },
{ username: "bob", password: "bob123" },
{ username: "james", password: "james123" },
{ username: "john", password: "john123" },
{ username: "robin", password: "robin123" },
{ username: "ted", password: "ted123" }
]

hashed_users = Crud.create_secure_users(users)
puts hashed_users

Running the script:

ruby main.rb

Output:

Crud module used
{username: "adam", password: "$2a$12$UbQ6bVjFF5AtphY08eMKpOk2d79r6FjLxNxJfBDWy4jjN.PaQsWMK"}
{username: "alex", password: "$2a$12$PQA5pHlI9fGH5jiwJ5XrguiN2gvJb2XGw7/kBTVNS2qXmRze3ntY."}
{username: "bob", password: "$2a$12$.3CjelhDXP9QiGwv.0Qh.ON3cpaQR5XlvVH8cKA1/fRPr/40dSwyG"}
{username: "james", password: "$2a$12$lvTQPD1hn7HQLfaJW4mFmO1nB4Xqgop79q7JpZK6XkRBQFDdc1pJC"}
{username: "john", password: "$2a$12$wb0bAjc8BGhWHmMQEJEZxeFDvZ7bLLaNmyubNsD0X7qExMm7Pe6QG"}
{username: "robin", password: "$2a$12$o5XpNQjCZy/Zy.tFkeaSseqwab9Jh8MNlqUATiH9E2qHrV9Qj0aom"}
{username: "ted", password: "$2a$12$g7Lxzf2v2LJ5DiQwS7N31OA3cGaKAJjtWAYQMbW3sIIIzm/34EFTq"}

Instance Method

Instance methods are defined without self.. To use them, you include the module in a class. This is called a mixin, and it allows objects of the class to access the module methods.

Sample Crud module:

## crud.rb 

module Crud
require 'bcrypt'
puts "Crud module used"

def create_hash_digest(password)
BCrypt::Password.create(password)
end

def verify_hash_digest(password)
BCrypt::Password.new(password)
end

def create_secure_users(list_of_users)
list_of_users.each do |user_record|
user_record[:password] = create_hash_digest(user_record[:password])
end

list_of_users
end

def authenticate_user(username, password, list_of_users)
list_of_users.each do |user_record|
if user_record[:username] == username && verify_hash_digest(user_record[:password]) == password
return user_record
end
end

puts "Incorrect credentials."
end
end

To use the module methods as instance methods, you need to include the Crud module in the class and create an object of that class. In student.rb, the class Student includes Crud, so each student object can call methods like create_hash_digest.

## student.rb

require_relative 'crud'

class Student
include Crud
attr_accessor :first_name, :last_name, :email, :username, :password

def initialize(first_name, last_name, email, username, password)
@first_name = first_name
@last_name = last_name
@email = email
@username = username
@password = password
end

def to_s
"Full name: #{@first_name} #{last_name}"
end
end

student1 = Student.new("Alex", "Smith", "alex.smith@abc.com", "alsmith", "alex123")

hashed_pw_student1 = student1.create_hash_digest(student1.password)
puts hashed_pw_student1

Running the script:

ruby student.rb 

As expected, the script prints the banner Crud module used and outputs the hashed password.

Crud module used
$2a$12$GD/iKEv6l5aSuMpcQ3Zgvu5ORE.0DJrSvLF6mm91aCVrp40kUI032

This confirms that the Student object can access methods from the Crud module and shows how mixins let instance objects use reusable module methods.