Credits: dnsimple.com

How to build a basic Hanami auth

Important: This article uses Hanami 0.9.

In this article, we are creating an application called MyApp with an web app inside it, ok? So, the goal here will be having a screen where an user can sign in, an action which he can sign out, and a script to restrict his access if he is not signed in.

Requirements

  • I will assume you already have an User entity and its database table created with at least email and password fields.
  • Also, I am going to use BCrypt module to decrypt the password, so your password need to be encrypted using BCrypt::Password.create("pass")
  • You already have a page for after user signed in. Here I am not going to define it, but I will call it as home .

To build a basic Hanami auth is pretty simple. First of all, we are going to create the sign in form visualization, with no authentication rule. All you need to do is:

Create an action for session to have the visualization of the form working

Don’t worry about the #authenticate! method below. I am going to explain it after.

# file: my_app/apps/web/controllers/sessions/new.rb
module Web::Controllers::Sessions
class New
include Web::Action
    def call(_); end
    private
    def authenticate!; end
end
end

Create a view for the new session action

Here, we will expose a form object to our template.

# file: my_app/apps/web/views/sessions/new.rb
module Web::Views::Sessions
class New
include Web::View
    def form
Form.new(:session, routes.sessions_path)
end
end
end

Create a template for the sign in screen

# file: my_app/apps/web/templates/sessions/new.html.erb
<%=
form_for form do
div do
label "Email"
text_field :email
end
      div do
label "Password"
password_field :password_plain
end
      submit 'Sign in'
end
%>

Create a route to access this action, view and template

Just add this line below in your existent route file.

# file: my_app/apps/web/config/routes.rb
resources :sessions, only: [:new]

If everything is fine, you will be able to see the sign-in form by accessing http://localhost:2300/sessions/new


Now, we are going to create the action which you receive those login data from user.

Create an action for session creation

Here we have the same #authenticate! method. Don’t worry, just define it, we are almost getting there.

We are just going to receive email and password params coming from the form above, and try to find an user with the email. If we find it, we will test his password. If it is right, we are going to save his id in a session, and redirect to the home page. If not, we are going to send him to the same sign in form.

# file: my_app/apps/web/controllers/sessions/create.rb
module Web::Controllers::Sessions
class Create
include Web::Action
    def call(_)
user = UserRepository.new.find_by_email(requested_email)
      if !user.nil? && password_correct?(user)
session[:user_id] = user.id
redirect_to routes.home_path
else
redirect_to routes.new_session_path
end
end
    private
    def password_correct?(user)
BCrypt::Password.new(user.password) == requested_password
end
    def requested_email
params[:session][:email]
end
    def requested_password
params[:session][:password_plain]
end
    def authenticate!; end
end
end

We are using a home_path to redirect user, but you need to change it with the correct path in your application to sent your user after he successfully logged in. After, we need to create the UserRepository#find_by_email method. So, let’s do it:

# file: my_app/lib/my_app/repositories/user_repository.rb
class UserRepository < Hanami::Repository
def find_by_email(email)
users
.where(email: email)
.as(User)
.one
end
end

Define one more route for sessions

Just change your line with the session route in you route file to be like this:

# file: my_app/apps/web/config/routes.rb
resources :sessions, only: [:new, :create]

Ok, now we already have our sign in working. But, we need to build a basic authentication control for when user is not logged in.

Defining authentication control

This script will be called before every action in your application. It will try to find an User based on the user_id stored on the session. If the session is empty or the user_id is incorrect, we won’t find any user and this script will redirect him to the sign in screen. If the user is found, we do nothing and let the action flow.

# file: my_app/apps/web/controllers/authentication.rb
module Web
module Authentication
def self.included(action)
action.class_eval do
before :authenticate!
expose :current_user
end
end
    private
    def authenticate!
redirect_to routes.new_session_path unless authenticated?
end
    def authenticated?
!!current_user
end
    def current_user
@current_user ||= UserRepository.new.find(session[:user_id])
end
end
end

Do you remember all those #authenticate! methods we defined since the beginning of this article? Here is the explaining: before every action, we will call this method. In this script above, we redirect the user to the sign in screen if we can’t find a session for him. But, in some cases we don’t want to redirect him, like when he is trying to reach the sign in screen, or in an about page, for example. So, what we need to do is override this method to say we don’t want to check for authentication. That’s what we made before. We define an #authenticate! method that doesn’t do nothing. Simple?

No, let’s configure our app to call this script before every action.

Configuring authentication script

We are going to put a new line in an existent Hanami script of our app. So, you need to look for the block below and put the new line in it.

# file: my_app/apps/web/application.rb
# ..stuff..
# look for this block
controller.prepare do
include Authentication # include this line in it
end
# ..stuff..

Perfect! We already have our authentication control and our sign in form. Now, we need to sign out the user.

Create an action to sign out the user

module Admin::Controllers::Sessions
class Destroy
include Admin::Action
    def call(_)
session[:user_id] = nil
redirect_to routes.new_session_path
end
end
end

That’s it. Our action just need to empty the user_id on our session and redirect him to a page. In this case, it is the sign in page. Now, let’s create the route for this action.

Create the sign out route

Just add the line below in the route file.

# file: my_app/apps/web/config/routes.rb
delete 'sessions', to: 'sessions#destroy', as: :session

Create a button for sign out

This code it is just an example of how you can create a button. You just need to put this code in a template partial and call it in every template you need to show the button.

<%=
form_for :session, routes.session_path, method: :delete do
submit 'Sign out'
end
%>

Aanndd, we finished! \o/

Ok, this article is just to illustrate how a basic mechanism of authentication works. But, there are a lot of things we could make better. So, don’t be shy to reorganize this code, put something in interactors or make the things beauty. If you have any trouble, just let me know leaving a comment.

Important!

I am building an open source Hanami application called Pinfluence. There are a lot of example of how to get somethings working with Hanami and you are free to get some help in there. Also, I need some help to build more features and fix some bugs. So, feel free to join me and contribute with an open source application built with Hanami =)

Github: https://github.com/prosi-org/pinfluence

See you!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.