Posts

Rails authentication with Devise

In this tutorial, we will be dealing with Rails authentication with Devise.

If you don’t want to use Devise for authentication, you may refer to the article create basic authentication in Ruby on Rails instead.

Devise has been around for some time now and have been used and reviewed thousands of times and still receives security updates to date.

Not only that, it is also composed of other features such as reset password, email integration, and many more that can be a pain when creating your own authentication system.

Step 1: Initialize a Rails application

You can initalize a Rails application by running the following commands in the terminal.

rails new devise-app
cd devise-app

Step 2: Install the Devise gem

In your Gemfile add the following:

gem 'devise'

And run the following in the terminal.

bundle install
rails g devise:install

This will install and initialize the Devise gem in our Rails application.

Step 3: Create your model for authentication

In this example, we would be using User as our model for authentication.

Run the following commands in the terminal.

rails g devise user
rails db:migrate

This will create a migration file for our User model then migrate it to complete.

Step 4: Add messages and navigation link to views

In your app/views/layouts/application.html.erb, add the following:

<% if user_signed_in? %>
  Logged in as <strong><%= current_user.email %></strong>.
  <%= link_to 'Edit Profile', edit_user_registration_path %> |
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %>
<% else %>
  <%= link_to 'Sign up', new_user_registration_path %> |
  <%= link_to 'Sign in', new_user_session_path %>
<% end %>
<%= notice if notice.present? %>
<%= alert if alert.present? %>

This will show the current signed in user, a link to edit profile, a link to sign-out if there is a current user and will show a sign-up link and sign-in link instead otherwise.

The notice and alert are used to notify the user for the actions that have been made.

Step 5: Register a user

Now that we have a model, we can now register a new User. The Devise gem already created everything we need such as routes, models, etc. for our authentication to work.

Run your Rails server by running the following in the terminal.

rails s

Navigate to http://localhost:3000/users/sign_up in your browser and you should see a form.

Fill up the form and click Sign up.

It is up to you now to create your own views and to see all available routes generated by Devise you may run the following command in the terminal.

rake routes

That’s it, we have now created a Rails authentication with Devise.

Test Doubles: Stubbing & Mocking

What are test doubles?

Test doubles are a substitute for production apps used for testing purposes. There are at least five (5) types of test doubles according to Gerard Meszaros but we are going to focus on two (2) which are stubbing and mocking.

The difference between stubbing & mocking

Stubbing

  • Doesn’t fail.
  • Makes sure that the method returns the correct value.
  • Returns hard-coded values.
  • Answers the question ‘What is the result?’.

Mocking

  • Can fail.
  • Runs the actual method and making sure everything is correct before returning the correct value.
  • Returns calculated values.
  • Answers the question ‘How the result has been achieved?’.

Why use stubbing & mocking?

  • To prevent tests failing intermittently due to connectivity issues.
  • For faster test suites.
  • To avoid hitting API limits when using 3rd party APIs.
  • To prevent creating/updating/deleting real data from tests.

Real life example

Imagine your kid has a glass plate and he starts playing with it. Now, you’re afraid that it will break, so you gave him a plastic plate instead. The plastic plate is mocking the glass plate. (same behavior and usage)

Now, say you don’t have a plastic replacement, so you can say to your kid “If you continue playing with it, it will break!”. The quoted words are a stub. (you predefined the outcome in advance)

Source: https://stackoverflow.com/a/53189007/9375533

Code example

Imagine that we have a simple calculator app that uses 3rd party API, in this case, our 3rd party API is math.js.

The thing is, we don’t want to make a request to math.js’s endpoint every time we ran our tests as this will make our tests slow since we need to wait for the response before we can proceed to the next test suite.

The tests can also fail if we don’t have an internet connection and we also might hit the API limit of their free service.

Without stubbing & mocking

result = '3*3' # EXAMPLE 1
result = '4*4' # EXAMPLE 2
uri = URI "https://api.mathjs.org/v4/?expr=#{result}"

expect(Net::HTTP.get(uri)).to eq(9)
# EXAMPLE 1 RETURNS 9 (SUCCESS)
# EXAMPLE 2 RETURNS 16 (FAILS)

With stubbing

result = '3*3' # EXAMPLE 1
result = '4*4' # EXAMPLE 2

stub_request(:get, "https://api.mathjs.org/v4/?expr=#{result}").to_return :body => "9"

uri = URI "https://api.mathjs.org/v4/?expr=#{result}"

expect(Net::HTTP.get(uri)).to eq(9)
# EXAMPLE 1 RETURNS 9 (SUCCESS)
# EXAMPLE 2 RETURNS 9 (SUCCESS)

What we did here is, we basically stubbed some data to the endpoint that we are going to call for our test.

In this case, we stubbed data to the endpoint with a body of 9. Which means, whenever we call the endpoint that we stubbed it will always return 9.

With mocking

result = '3*3' # EXAMPLE 1
result = '4*4' # EXAMPLE 2

expect(eval(result)).to eq(9)
# EXAMPLE 1 RETURNS 9 (SUCCESS)
# EXAMPLE 2 RETURNS 16 (FAILS)

We know that the endpoint simply calculates the expression params then return its result. Now we can mock it ourselves. eval(result) is the mock of the API endpoint as it returns the same output exclusively for our test suites.

That’s what I can share about stubbing & mocking. I hope you learned something. Using a test double type really depends on your needs. In my case, I use both stubbing & mocking at the same time.

Rails file upload using Active Storage

In this tutorial, we would be learning to upload files in Rails. Rails file upload have become easier since the release of Active Storage. It allows us to handle file uploads without using any gem.

This tutorial aims to deliver the simplest way to integrate Rails file upload. We would be skipping the details on how Active Storage works in the back and focus on how to integrate it correctly.

If you are already on an existing project and has a model that already has a CRUD operation then you may skip to Step 3.

Step 1: Create a new Rails project.

In your terminal run the following commands.

rails new file-upload
cd file-upload

This will simply create a new Rails project and change our current directory in the newly created project.

Step 2: Create a CRUD.

Since our focus is on file upload, we would be using scaffold to create our CRUD operations.

Scaffolding requires the following command in the terminal.

rails g scaffold post title:string content:text

This will create a Post model with a title and content columns, a database migration, and all views.

Step 3: Install Active Storage.

You need to run the following command in the terminal.

rails active_storage:install
rake db:migrate

The command creates a database migration that hold the necessary data and makes file uploading possible. Since we have a new database migration, we would need to migrate it.

Step 4: Add the necessary association.

In the file app/models/post.rb add the following association.

class Post < ApplicationRecord

  has_one_attached :image

end

This will tell that our model allows an attachment image. The word image is user-defined and can be changed to anything you want.

Since we are using has_one_attached, we are only expected to attach one image per record. If you want to attach many files in a single record you may want to use has_many_attached instead.

Step 5: Add the file upload field in the form.

The scaffold the we ran in Step 2 includes the views needed in our CRUD.

In the file app/views/posts/_form.html.erb, inside the form_with, add the following.

<div class="field">
  <%= form.label :image %>
  <%= form.file_field :image %>
</div>

This will add a file upload field in the form.

Step 6: Process the new field in the controller.

All changes in this step will be done in the file app/controllers/posts_controller.rb.

First, we would need to whitelist the new field in the parameters. This is necessary to prevent attackers from passing malicious parameters to our controller.

def post_params
  params.require(:post).permit(:title, :content, :image)
end

Second, we would need to attach our image in the controller’s create method.

# POST /posts
# POST /posts.json
def create
  @post = Post.new(post_params)
  @post.image.attach(post_params[:image])

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post, notice: 'Post was successfully created.' }
      format.json { render :show, status: :created, location: @post }
    else
      format.html { render :new }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

Notice that we just added @post.image.attach(post_params[:image]) in the create method.

Third, we also need to attach our image in the update method.

# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
  @post.image.purge
  @post.image.attach(post_params[:image])

  respond_to do |format|
    if @post.update(post_params)
      format.html { redirect_to @post, notice: 'Post was successfully updated.' }
      format.json { render :show, status: :ok, location: @post }
    else
      format.html { render :edit }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

In the update method we added @post.image.purge to ensure that the old attachments are deleted before we attach the new file. The next line after purge is the same as what we did in create method.

Step 7: Show the uploaded file in the view.

Now that we are able to attach files on our records, we would want to view them in the view right?

In your file app/views/posts/show.html.erb, add the following.

<p>
  <% if @post.image.attached? %>
    <p>
      <strong>Image:</strong>
      <br>
      <%= image_tag @post.image %>
    </p>
  <% end %>
</p>

It will check if there is a file attached and if it does it will show the file.

You may want to run your app using rails s and navigate to http://localhost:3000/posts to confirm that everything works.

That’s it, we have now implemented a Rails file upload using Active Storage. Take note that all files will be saved on your local disk, you can also change this but that would be for another tutorial as this aims to deliver the most basic Rails file upload.

How to create basic authentication in Ruby on Rails

In this tutorial, we will be creating a basic authentication in Ruby on Rails.

Step 1: Adding a password_digest field to the model.

This field will be used for authenticating the user.

Step 1.1-A: Coming from a fresh Rails project.

If you are doing this from a fresh project then you must first create a User model by running rails g model User in the terminal.

This will create a file in db/migrate/xxxxx_create_users.rb where xxxxx is the current date and time you ran the command.

In that file add the following:

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

Return to your terminal and run:

rake db:migrate

This will create a users table in the database with columns username and password digest.

Step 1.1-B: Coming from an existing Rails project.

If you are doing this from an existing project then you must add password_digest field to your existing model.

In this tutorial, we will be using User as the model. Feel free to change it on your preference.

Run the following command in the terminal:

rails g migration add_password_digest_to_users

This will create a file in db/migrate/xxxxx_add_password_digest_to_users.rb

In that file add the following:

class AddPasswordDigestToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :password_digest, :string
  end
end

Return to your terminal and run:

rake db:migrate

This will add a string column password_digest to your users table in the database.

Note:

In this step, you may notice that ActiveRecord::Migration[5.2] has 5.2 in it, you must change it to the correct version, otherwise, this might not work.

You might want to check your Rails version by running the following command in the terminal:

rails -v

This tutorial is created using Rails 5.2.3.

Step 2: Creating routes for the app.

Open config/routes.rb in your text editor.

In this file, we will define the links in our app. For this tutorial, we will be creating six (6) routes. one (1) that needs authentication and five (5) unauthenticated routes.

Rails.application.routes.draw do

  # AUTHENTICATED ROUTES
  get '/dashboard' => 'dashboard#index' # THIS WILL BE THE DASHBOARD OF THE USER

  # UNAUTHENTICATED ROUTES
  get '/sign-up' => 'users#new' # THIS IS WHERE THE USER WILL REGISTER
  post '/save-user' => 'users#create' # THIS WILL BE THE PROCESS OF REGISTERING THE USER

  get '/' => 'sessions#new' # THIS IS WHERETHE USER WILL SIGN IN
  post '/' => 'sessions#create' # THIS WILL BE THE PROCESS OF SIGNING IN THE USER
  delete '/sign-out' => 'sessions#destroy' # THIS WILL BE THE PROCESS OF SIGNING OUT THE USER

end

Step 3: Creating controllers for the routes.

At this point, our app will raise an error because the routes we defined doesn’t have a matching controller.

We can see that we used three (3) different controllers in Step 2. These are the dashboard, users, and sessions.

To create a matching controller for these routes you can run the following command in the terminal. One (1) at a time.

rails g controller dashboard
rails g controller users
rails g controller sessions

This will create files in app/controllers directory.

In the dashboard controller app/controllers/dashboard_controller.rb add the following method:

def index
end

In the users controller app/controllers/users_controller.rb add the following method:

def new
end

def create
end

In the sessions controller app/controllers/sessions_controller.rb add the following method:

def new
end

def create
end

def destroy
end

If you observe the routes, it does not only matches to the controller but also matches to the methods in that controller.

The creation of method above ensures that our controller matches our routes.

Step 4: Creating the dashboard page.

In the app/views/dashboard/index.html.erb paste the following code (create the file if it does not exists):

DASHBOARD

This page will simply return a text DASHBOARD when /dashboard is accessed in the browser.

Step 5: Creating a sign up page.

From the steps above, we know that the users controller handles these processes.

In your app/controllers/users_controller.rb, inside the new method, add the following:

@user = User.new

In the app/views/users/new.html.erb paste the following code (create the file if it does not exists):

<h1>Sign Up</h1>

<%= form_with :model => @user, :url => '/save-user' do |f| %>
  <%= f.label :username %>
  <br>
  <%= f.text_field :username %>
  <br>
  <br>
  <%= f.label :password %>
  <br>
  <%= f.password_field :password %>
  <br>
  <br>
  <%= f.label :password_confirmation %>
  <br>
  <%= f.password_field :password_confirmation %>
  <br>
  <br>
  <%= f.submit %>
<% end %>

This will show an HTML form when we access the link /sign-up in our browser that will make a POST request to /save-user when submitted.

Now, we are going to edit the method that will handle the data from our form.

In the same controller file add the following method:

private

def user_params
  params.require(:user).permit :username, :password, :password_confirmation
end

This is to let to know that our app only permits username, password, and password_confirmation which is coming from our form to prevent malicious parameters to be processed.

In the create method add the following code:

@user = User.new user_params

if @user.save
  session[:user_id] = @user.id

  redirect_to '/dashboard'
else
  redirect_to '/sign-up'
end

Basically, we are just creating a new instance of User and its parameters.

If it was saved successfully then we will set the newly registered user to be the current user and redirect it the dashboard but returns back to sign up page if the registration fails.

Step 6: Creating the sign in page.

In the app/views/sessions/new.html.erb paste the following code (create the file if it does not exists):

<h1>Sign In</h1>

<%= form_with :url => '/' do |f| %>
  <%= f.label :username %>
  <br>
  <%= f.text_field :username %>
  <br>
  <br>
  <%= f.label :password %>
  <br>
  <%= f.password_field :password %>
  <br>
  <br>
  <%= f.submit %>
<% end %>

Now that we created the form, we now have to authenticate the credentials if it matches a record in the database.

In your app/controllers/sessions_controller.rb, inside the create method, add the following:

@user = User.find_by :username => params[:username]

if @user && @user.authenticate(params[:password])
  session[:user_id] = @user.id

  redirect_to '/dashboard'
else
  redirect_to '/'
end

What the code does is that it finds the user with the supplied username and authenticates its password.

It sets the session and redirects to the dashboard if it succeeds then redirects back it to the sign in page if it doesn’t.

Step 7: Creating the sign out function.

In your app/controllers/sessions_controller.rb, inside the destroy method, add the following:

session[:user_id] = nil

redirect_to '/'

We are just going to set the session[:user_id] that we defined in the sign up and sign in back to its original value which is nil and redirects back the user to the sign in page.

Step 8: Creating the helper methods.

In your app/controllers/application_controller.rb, add the following:

helper_method :current_user



def current_user
  @current_user ||= User.find_by :id => session[:user_id]
end

def authorize
  redirect_to '/' unless current_user
end

A helper method is a method that we can access in our views. The helper method current user holds the data of the currently authenticated user.

The authorize method is a method that we can call to our routes that needs a user to be authenticated before allowing access to a resource.

In this case, we defined /dashboard to be the only route that needs authentication before being accessed.

In your app/controllers/dashboard_controller.rb, add the following:

class DashboardController < ApplicationController

  def index
    authorize

    # YOUR OTHER CODE HERE
  end

end

This simply calls authorize method and redirects back to the user to the sign in page if no user is authenticated.

If you want the whole controller to be authenticated instead:

class DashboardController < ApplicationController

  before_action :authorize



  def index
  end

  def method1
  end

  def method2
  end

end

This will authenticate not just the index method but also method1 and method2.

Step 9: Adding the links in the view.

We created all this routes, pages, and stuff but we didn’t have links to access this pages at all without manually typing the links in the browser.

In your app/views/layouts/application.html.erb, add the following inside the <body> above the <%= yield %>:

<% if current_user %>
  Hi <%= current_user.username %>! | <%= link_to 'Sign Out', '/sign-out', :method => :delete %>
<% else %>
  <%= link_to 'Sign In', '/' %> | <%= link_to 'Sign Up', '/sign-up' %>
<% end %>
<br>
<br>

What it does is printing the name of user and a sign out link if it’s authenticated and sign in and sign up links if it’s unauthenticated.

Step 10: Installing bcrypt gem.

Our app may look complete at this point, but if you run it at this state, you will just encounter a lot of errors because we are lacking the core function of this tutorial.

In your Gemfile uncomment the following:

gem 'bcrypt', '~> 3.1.7'

But if it does not exists, add it instead.

In your terminal run:

bundle install

In your app/models/user.rbadd the following:

class User < ApplicationRecord

  has_secure_password

end

Step 11: Run the app.

In your terminal run:

rails s

You should be able to see the sign in page as your startup page if you access http://localhost:3000/ in your browser.

That’s it. This is just a basic authentication in Rails for learning purposes.