Authentication

HTTP is stateless meaning when request is made and server sends back the response, cycle is complete and know information about that cycle is remember.To help in storing information sessions.

With respect to RESTful resourcing we will make session a resource which will create during login so as the and destroy it so as to logout

Create the home page

Before we start our authentication let us quickly create landing page,it will the root of our application.

1
2
3
$ git checkout -b landing-page origin master
$ git pull origin master
$ rails g controller Statics home

Let us adjust our routes like so:

config/routes.rb

1
2
## truncated for brevity
root to: 'statics#home

We create a simple page which allow to sign up like so:

app/views/home.html.erb

1
2
3
<p>Welcome to mpesa app<p>

<p><%= link_to "Sign up now!", new_user_path %><p>

User authentication

Authentication involve the identification of a system, this is achieved by user telling us what who you are and what you know.Who you are in this should be unique to a particular user for instance email,KRA pin in our case it can be phone number.This usually in public domain after everybody knows.

What you know is something personal to you.Most cases it usually password.This usually private a combination of what you know and what you have can be used to identify a particular user.

To achieve this in our application user,user will provide her phone number and password which will compare with what we have in our database.

1
$ git checkout -b user-login origin/master

Let us generate session controller like so:

1
$ rails generate controller Sessions 

If user details matches those in database we to create a session for her if not we should render login page with message invalid phone number or password.

Adjust the routes like:

config/routes.rb

1
2
# truncated for brevity
get '/login', to: 'sessions#new', as: 'login'

Adjust session controller to render login page/new session page like so:

*app/controllers/sessions_controller.rb

1
2
3
4
class SessionsController < ApplicationController
  def new
  end  
end

Let us create the login page like so:

app/views/sessions/new.html.erb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<%= form_with scope: :session, url: login_path, local: true do |f| %>
<p>
  <%= f.label :phone_number %>
  <%= f.text_field :phone_number%></br>
</p>
<p>
  <%= f.label :password %>
  <%= f.password_field :password%></br>
</p>

<p><%= f.submit "Log in"%></p></br>
<% end %>
<p>New user? <%= link_to "Sign up now!", new_user_path %></p>

The page allow user to enter phone number and password and incase they are new users a link to signup.

Let us enable our users their data to the server for verification adding the post method

config/routes.rb

1
2
# truncated for brevity
post '/login', 'sessions#create'

For us to creation to the user we must make sure phone number and password provide both match.

There we will phone number to check if user exist and password to confirm if user is who she say she is. app/controller/sessions_controller.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# truncated for brevity
def create
  user =   User.find_by(phone_number: session_params[:phone_number])
  if user && user.authenticate(session_params[:password])

  else  
    render 'new'
  end
end
private
def session_params
  params.require(:session).permit(:phone_number, :password)
end

In the above create action we find user by phone number provide this either returns the user if phone number given belongs to an existing user or nil if user is not available.

authenticate method is provide by the has_secure_password,it compare the unecrypted version of password_digest with password provided by the user and return true if they are the same or false otherwise.

For now if both find_by and authenticate return truthy value we do nothing but if either of them return falsy value we render login page.

The login page should be rendered incase of authentication failure and user should be notified so.

We can achieve this using a flash.Flash is used to send messages, either information or errors https://guides.rubyonrails.org/action_controller_overview.html#the-flash

We can inform the user using flash like so:

app/controller/sessions_controller.rb

1
2
3
4
5
6
7
8
def create
  user =   User.find_by(phone_number: session_params[:phone_number])
  if user && user.authenticate(session_params[:password])    
  else  
  flash.now[:error] = 'Invalid email/password combination'
    render 'new'
  end
end

Let adjust our home page to give our user ability to sign in if they already have an account.

app/views/home.html.erb

1
2
3
# truncated for brevity
<p> Already have an account?<%= link_to "Sign In", login_path %><p>
<p>New user? <%= link_to "Sign up now!", new_user_path %></p>

Creating session

For us to create session for the user will user an helper which will be a module.create file

app/helpers/sessions_helper.rb and inside add the following.

app/helpers/sessions_helper.rb

1
2
3
4
5
module SessionsHelper
    def login(user)
    session[:user_id] = user.id
    end
end

session[:user_id] = user.id creates temporary cookies with encrypted user id.This helps in creation of session for a particular user.session in this case in rails a method and has nothing to with our session control.To learn more about session.

For method login to be available to every controller in our application let us include the sessionHelper module in our ApplicationController like so.

app/controllers/application_controller.rb

1
2
3
class ApplicationController < ActionController::Base
include SessionsHelper
end

During login if the login was unsuccessful, we rendered login page and gave user an error message but incase of successful let us redirect them to home page displaying their name.

Showing the current user

The current user is the user in session i.e that who is logged in, we can identify this uniquely using their id and since we said user_id in session is same as the id of the user therefore we can retrieve him or from the database like so:

app/helpers/sessions_helper.rb

1
2
3
4
5
6
module SessionsHelper
# truncated for brevity
  def current_user
   @current_user ||= User.find_by(id: session[:user_id])
  end
end

The above method check if @current_user already having a value if it doesn't it assign the value, which in this case will the user whose id is in session.If it already has it won't assign it.

Let us use condtional render to display current_user's first name.If they are in session otherwise give them display Sign in and Sign up link like so:

app/views/home.html.erb

1
2
3
4
5
6
7
<p>Welcome to mpesa app<p>
<% if current_user.present? %>
You are logged in as <%= @current_user.first_name%>
<%else%>
<p> Already have an account?<%= link_to "Sign In", login_path %><p>
<p>New user? <%= link_to "Sign up now!", new_user_path %></p>
<%end%>

Logging out

When user log our we delete the session.This details information about the current user stored in the browser. The let us setup our route like so:

1
2
  # truncated for brevity
  delete '/logout', to: 'sessions#destroy'

Let us define the destroy method in our session controller like so:

app/controllers/sessions_controller.rb

1
2
3
4
def destroy
logout
redirect to root_path
end

We have not yet define logout method which is called inside destroy method let us do so

app/helpers/sessions_helper.rb

1
2
3
4
5
# truncated for brevity
def log_out
session.delete(:user_id)
@current_user = nil
end