At Monterail we like to try new stuff. We try new languages and new (maybe not so new) frameworks.

Hanami (formerly Lotus) is a Ruby web framework created by Luca Guidi and community.

Probably the most problematic thing for people trying hanami after experience with rails is changing your mind away from the rails way . I can write many good things about hanami, but now I want to resolve one problem - authentication. The popular question is, What about users ? In rails, we have devise which is a great gem but it is Rails only. In hanami, you can use other (non rails) user solutions. But sometimes you don't need all this devise stuff and omniauth is enough (like in our internal apps).

Step 1 - create new project

I guess you have some application already, but I'll create a new one.

hanami new hanami_oauth --test=rspec

For this tutorial we don't need any db (File System is the default adapter, so everything will be saved in file).

Step 2 - add user

The generator will create all necessary files.

bundle exec hanami generate model user

create lib/hanami_oauth/entities/user.rb create lib/hanami_oauth/repositories/user_repository.rb create spec/hanami_oauth/entities/user_spec.rb create spec/hanami_oauth/repositories/user_repository_spec.rb

Hanami uses hanami-model , but you can replace it with any other ORM. As we do not have any database we will not need migrations, but we must make some changes to our model.

Edit file: /lib/hanami_oauth.rb

Hanami :: Model . configure do . . . mapping do collection :users do entity User repository UserRepository attribute :id , Integer attribute :name , String attribute :github_id , String attribute :email , String end end end

Next, edit file: lib/hanami_oath/entities/user.rb

class User include Hanami :: Entity attributes :id , :name , :github_id , :email end

Step 3 - add OmniAuth configuration

To connect our app with github we need to add 2 gems to our Gemfile

gem "omniauth-github" gem "warden"

Add the github keys to .env . You can generate your keys here.

GITHUB_CLIENT_KEY = "xxx" GITHUB_CLIENT_SECRET = "xxx"

Now let's prepare our application for OAuth.

edit: /apps/web/application.rb

Uncomment the following:

sessions :cookie , secret: ENV [ 'WEB_SESSIONS_SECRET' ]

Also add this:

middleware . use Warden :: Manager do | manager | manager . failure_app = Web :: Controllers :: Session :: Failure . new end

This:

middleware . use OmniAuth :: Builder do provider :github , ENV [ "GITHUB_CLIENT_KEY" ], ENV [ "GITHUB_CLIENT_SECRET" ] end

And this:

controller . prepare do include Web :: Authentication end

You can add before :authenticate! if you want to run it before all actions.

Step 4 - authentication controller

We need a global file and 3 actions (for new session, failure and destroy). At this moment views are redundant so we use generator with the --skip-view argument.

bundle exec hanami generate action web session#new --skip-view bundle exec hanami generate action web session#failure --skip-view bundle exec hanami generate action web session#destroy --skip-view

After that we should edit our router: /apps/web/config/routes.rb

get "/auth/failure" , to: "session#failure" get "/auth/signout" , to: "session#destroy" get "/auth/:provider/callback" , to: "session#new"

If you want to see all routes you can type: bundle exec hanami routes in the terminal.

After that create this file: /app/web/controllers/authentication.rb

module Web module Authentication def self . included ( action ) action . class_eval do expose :current_user end end def current_user @current_user ||= warden . user end def warden request . env [ "warden" ] end def authenticate_user! redirect_to "/auth/:provider/callback" unless current_user end end end

Ok, so now we should create our actions.

Edit this file: /apps/web/controllers/session/failure.rb

module Web::Controllers::Session class Failure include Web :: Action def call ( _params ) status 404 , "Not found" end end end

And this one: /apps/web/controllers/session/new.rb

module Web::Controllers::Session class New include Web :: Action def auth_hash request . env [ "omniauth.auth" ] end def call ( params ) user = UserRepository . auth! ( auth_hash ) warden . set_user user redirect_to "/" end def warden request . env [ "warden" ] end end end

And this one: /apps/web/controllers/session/destroy.rb

module Web::Controllers::Session class Destroy include Web :: Action def call ( params ) warden . logout redirect_to "/" end end end

Step 5 - add auth! to UserRepository

Again, edit this file: /lib/hanami_oauth/repositories/user_repository.rb

class UserRepository include Hanami :: Repository def self . auth! ( auth_hash ) info = auth_hash [ :info ] github_id = info [ :uid ] attrs = { name: info [ :name ], email: info [ :email ], } if user = query { where ( github_id: attrs [ :github_id ]) }. first user . update ( attrs ) update user else create ( User . new ( attrs . merge ( github_id: github_id ))) end end end

Step 6 - add some views

We would probably like to check our authentication.

bundle exec hanami generate action web home#index

This will be enough.

We can set it as our main page in: apps/web/config/routes.rb get '/', to: 'home#index'

We created the first template in our app: /apps/web/templates/home/index.html.erb

<p> < % if current_user %> Hello, < %= current_user.name %> | < %= link_to "Sign out", "/auth/signout" %> < % else %> < %= link_to "Login with Github", "/auth/github" %> < % end %> </p>

Let's check our work

run hanami server in the terminal and open localhost:2300 you should see the following link Login with Github

Summary

If you have any problems, you can check the code on github. This application is very simple and does nothing except show the current user. But for this example, I think it's enough. You can use it with any other OAuth provider as well. I hope that after you overcome your first problem (authentication), delving into the hanami world will be pleasure.