Authentications with Omniauth

May 01, 2012
0db484da0a364dbadf287398f97a97db

Having a site that allows you to authenticate using a third-party service, such as Facebook, Twitter or Google, turns out to be very handy these days. One easy way to handle the authorizations in your Rails app is with the Omniauth Gem.

Omniauth allows you to integrate one or more of these providers (also called strategies) in your app. So after reading and reviewing some Omniauth articles, I decided to put the most relevant info in this post. In it, I´ll guide you on how to implement it in a Rails App using the Github and Twitter providers.

  1. Add the gem according with the provider you choose:

    Gemfile

    gem 'omniauth-github'
    gem 'omniauth-twitter'
    
  2. Create an application with your provider:

    For Github go to: http://github.com/account/applications, fill the form and click 'Create Application'

    Form image

    While your are on development, you can setup the URL and Callback URL to point at your local host as follows:

    Url: http://localhost:3000
    Callback URL: http://localhost:3000/auth/github/callback
    

    To create a Twitter application go to: https://dev.twitter.com/apps/new

    When you create a Twitter application, it doesn't allow you to set your url and your callback url to your localhost (due to security issues), but there are a few workarounds that you can do to test your app on your localhost:

    a) Take your localhost url i.e. http://localhost:3000/auth/twitter/callback and use a shortening service like http://goo.gl/, and point your application to that url, you'll have something like this: http://goo.gl/dH7Md

    b) Set your application callback to http://127.0.0.1:3000/auth/twitter/callback

    c) Use pow to run your app, and then use the url like i.e http://myapp.dev/auth/twitter/callback

    Once Finished, your twitter set up should look something like this:

    Form image

    Remember to change URL and Callback URL when going to production.

  3. Once you have your providers apps all set up, create an Omniauth initializer file, you will need the Client ID and Secret keys from the Github application, and the Consumer Key and Consumer Secret from your Twitter application:

    ../initializers/omniauth.rb

        ENV['GITHUB_KEY'] ||= 'Client ID'
        ENV['GITHUB_SECRET'] ||= 'Secret'
        ENV['TWITTER_KEY'] ||= 'Consumer Key'
        ENV['TWITTER_SECRET'] ||= 'Consumer Secret'  
    
        Rails.application.config.middleware.use OmniAuth::Builder do  
          provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET']  
          provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']  
        end  
    
  4. Add needed routes for your authentications:

    ../routes.rb

        match "/auth/:provider/callback" => "sessions#create"  
        match '/auth/failure', :to => 'sessions#failure'  
        match "/signout" => "sessions#destroy", :as => :signout  
    
  5. The next thing that we are going to need, if you don't have one yet, is to set up a controller to manage your sessions:

    ../sessions_controller.rb

        class SessionsController < ApplicationController  
          def create
            if session[:id]
              current_user.create_provider(auth_hash)  
            else
              auth = Authorization.find_or_create_by_provider(auth_hash)  
              session[:id] = auth.user_id   
            end
            redirect_to '/', :notice => "Signed in!"  
          end
    
          def destroy
            session[:id] = nil
            redirect_to root_url, :notice => "Signed out!"
          end
    
          def failure
            redirect_to '/', :alert => 'Sorry, something went wrong. Try again.'
          end
    
          protected
          def auth_hash
            request.env['omniauth.auth']
          end
        end  
    
  6. Now are going to need a couple of models, a User model and Authorization model. If you dont have those models yet, is time to create them! Keep in mind that we are going to need at least the following attributes:

    User Model

        name: string
        email: string
    

    Authorization Model

        provider: string
        uid: string
    

    Check the link to the Hash Schema at the end of this post to see all the aviable attributes

    In our User model, add the create_provider method (called in the create action of our sessions controller) along with the user data hash that we need to save:

        class User < ActiveRecord::Base
          has_many :authorizations
    
          def create_provider(auth_hash)
            unless authorizations.find_by_provider_and_uid(auth_hash["provider"],  
                    auth_hash["uid"])
              Authorization.create( :user => current_user,  
                    :provider => auth_hash["provider"], 
                    :uid => auth_hash["uid"] )
            end
          end
        end
    

    In our Authorization model, we need the find_or_create_by_provider method:

       class Authorization < ActiveRecord::Base
         belongs_to :user
         validates :provider, :uid, presence: true
    
         def self.find_or_create_by_provider(auth_hash)
           unless auth = find_by_provider_and_uid(auth_hash['provider'],  
                                 auth_hash['uid'])  
    
             user = User.create(name: auth_hash['info']['name'],  
                            email: auth_hash['info']['email'])  
    
             auth = create user:( user, provider: auth_hash['provider'],  
                            uid: auth_hash['uid'] )  
    
           end
           auth
         end
       end  
    
  7. We are going to need a few methods in our application controller, define three helper_methods, one to find the current user by its session id and one method that redirects to the root path if the user hasn't been authenticated yet. Also we need to add a before_filter that checks the callback url when our app is in production to avoid callback errors.

       class ApplicationController < ActionController::Base
         before_filter :check_uri
         helper_method :current_user, :authenticate_user!  
    
         def check_uri
           url = redirect_to request.protocol + 'www.' + request.host_with_port + 
                 request.fullpath if Rails.env.to_s == 'production' &&   
                 !/^www/.match(request.host) && ENV['DEV'].nil?
         end
    
         def current_user
           @current_user ||= User.find_by_id(session[:id])
         end
    
         def authenticate_user!
           if session[:id]
             current_user
           else
             redirect_to '/'
           end
         end
       end  
    
  8. At last, but not least, configure the view where you want the login links, it might be something like this:

    If the user is not logged in

       %h2 Welcome to the Omniauth Example... Login with your preferred provider!
       = link_to image_tag('github.png'), '/auth/github' 
       = link_to image_tag('twitter.png'), '/auth/twitter'  
    

    if the user is logged in

       %p
         You are logged in as #{current_user.name}
         = link_to 'Logout', '/signout'
    

Conclusions

As you can see, implementing Omniauth is pretty straight foward. I hope you found this post helpful. You can also take a look at the example app in Github

Usefull Links:

blog comments powered byDisqus