Tutorial: Integrating OmniAuth with Sinatra Application

As part of my GSoC project, my first task includes that user could login into their account on debci using their Debian Salsa account (collaborative development server for Debian based on the GitLab software).

The task is officially completed using OmniAuth library and while implementing it, I found that the documentation of OmniAuth is quite a mix-match and more focused on using it with Rails app and this gives me a idea to write a tutorial for people looking to integrate OmniAuth with Sinatra application. So here it is.

Now, depending on the provider, Omniauth requires specific strategy which are generally released individually as RubyGems. For this tutorial, I would be using omniauth-gitlab which I used for Debian Salsa in my project, omniauth-twitter and a developer strategy which could be used for project in development mode and comes with omniauth gem itself.

For simplicity purposes, I have included all routes and OmniAuth configurations in a single file app.rb.

Let’s start.

Register your application

This can be easily done – just head over to provider (Twitter, Salsa) website and find the option to create a new application and fill in the form. In the callback URL field, you need to append /auth/:provider/callback to whichever URL you used in the website field.

tutorial-register-app

The client-id and client-secret is obtained from the console which is used in further step to set up OmniAuth.

Gems

At the top of the file, we require the necessary gems of for our project

require 'sinatra'
require 'omniauth'
require 'omniauth-gitlab'
require 'omniauth-twitter'

Enable sessions

In order for OmniAuth to work and to store the logged in user across requests, sessions need to be activated and if activated, you have one session hash per user session.

configure do
    set :sessions, true
end

Set up OmniAuth configurations

OmniAuth::Builder Rack middleware build up your list of OmniAuth strategies for use in your application:

use OmniAuth::Builder do
    if development?
      provider :developer,
               fields: [:name],
               uid_field: :name
    end
    provider :gitlab, #client-id , #client-secret,
             scope: "read_user",
             client_options: {
               site: 'https://salsa.debian.org/api/v4/'
             }
    provider :twitter, #client-id , #client-secret,
end

Here, few things to note could be the extra options used with providers, as
In developer:
fields : to specify the form fields for login in developer mode and by default it has name and email.
uid_field: to specify that which field’s value could be obtained as uid and by default it is email.
In gitlab:
scope: to limit the scope of application, by default, the api scope is requested and must be allowed in GitLab’s application configuration.
client_options: to specify the server being used as client based on Gitlab software.
Note: In case you want to have a different callback url other than the default /auth/:provider/callback, it can be specified using redirect_url option in case of gitlab provider and accordingly update it in your Application Configuration at provider’s console.

Extra configurations

To redirect to auth/failure route in case of failure even in developer mode, following could be added:

OmniAuth.config.on_failure = proc do |env|
    OmniAuth::FailureEndpoint.new(env).redirect_to_failure
end

By default, OmniAuth will log to STDOUT but you can configure this. If you don’t want OmniAuth to log to STDOUT, following could be used:

OmniAuth.config.logger.level = Logger::UNKNOWN

Setting up routes

Login route

Starting with GET /login route, where you can specify the options available to login:

get '/login' do
    <<~HTML
    <form method='post' action='/auth/gitlab'>
    <input type="hidden" name="authenticity_token" value='#{request.env["rack.session"]["csrf"]}'>
    <button type='submit'>Login with Salsa</button>
    </form>
    <form method='post' action='/auth/twitter'>
    <input type="hidden" name="authenticity_token" value='#{request.env["rack.session"]["csrf"]}'>
    <button type='submit'>Login with Twitter</button>
    </form>
    <form method='post' action='/auth/developer'>
    <input type="hidden" name="authenticity_token" value='#{request.env["rack.session"]["csrf"]}'>
    <button type='submit'>Login with Developer</button>
    </form>
  HTML
end

The auth/:provider path is created and configured automatically by OmniAuth, so you just need to send the request to that paths and auth process will start.
As per the OmniAuth version 2.0, OmniAuth now defaults to only POST as allowed request_phase methods and authenticity_token is required to validate your requests so make sure to take care of this.

Callback routes:

On success from authentication, Omniauth will return the hash of information to the auth/:provider/callback in the Rack environment under the key omniauth.auth so this is what you can use in your desired way like creating a entry to your database and storing the current user in session params.

get '/auth/:provider/callback' do
    erb "
    <h1>Hello #{request.env['omniauth.auth']['info']['name']}</h1>"
end

post '/auth/developer/callback' do
    erb "
    <h1>Hello #{request.env['omniauth.auth']['info']['name']}</h1>"
end

Here, POST request method is used for developer strategy and GET request method for twitter and gitlab as that is how their working is defined in their respective strategies.

Failure route:

If user authentication fails on the provider side, OmniAuth will catch the response and then redirect the request to the path /auth/failure, passing a corresponding error message in a parameter named message.

get '/auth/failure' do
    halt(403, erb("<h2>Authentication Failed</h2><h4>Reason: </h4><pre>#{params[:message]}</pre>"))
end

Final result

That’s it, our application is all set to be tested. And this is how it will be working.

Tutorial

Here, I have already logged into my respective accounts so page for entring credentials does not show up but if the user has not logged into his account, he would be first asked to log in.

For complete code, you can check out: OmniAuth with Sinatra Tutorial

So this completes the tutorial. I hope it helps others who are looking to integrate their Ruby Applications with OmniAuth. If you have any feedback, feel free to let me know.

See you next time!