Social Network Authentications with Omniauth and Authlogic

As I discussed my experience developing an application in Rails 3 using authlogic and omniauth in my last post, Here is the tutorial as promised.

Step 1:

First of all you need to setup a Rails 3 application using authlogic gem. If you’re feeling some difficulty, try these tutorials for help:

Authlogic railcasts: http://railscasts.com/episodes/160-authlogic
Authlogic with Rails 3 tutorial: http://www.dixis.com/?p=352

Step 2:

Add the following line to your gemfile:

gem 'omniauth'

and then install the bundle

bundle install

Step 3:

Now create a ruby file in config/initializers. I’ve named it as omniauth.rb. Put the following code in it:

Rails.application.config.middleware.use OmniAuth::Builder do
    provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET'
    provider :facebook, 'APP_ID', 'APP_SECRET'
    # Mention other providers here you want to allow user to sign in with
end

* You can get the keys by creating an application on twitter and facebook each.

Step 4:

A user can be authenticated e.g., for twitter by redirecting him to:

http://HOST:PORT/auth/twitter

After authentication, user will be redirected back to

http://HOST:PORT/auth/twitter/callback

We can handle this callback and redirect user to a controller#action where we can process his registration and/or session handling as well as can fetch some useful data. For this, add the following lines in your routes.rb file:

resources :authentications
match '/auth/:provider/callback' => 'authentications#create'

Let’s add a couple of links on our login page which allow user to sign in via facebook or twitter:

<a href="/auth/twitter">Sign in with Facebook</a>
<a href="/auth/facebook">Sign in with Twitter</a>

Step 5:

Until here, we’ve nicely setup the user part of authentication. Now we’ve to handle the callback from social websites after authentication. Create a scaffold authentication. Here are required code snippets:

Authetications Migration:

class CreateAuthentications < ActiveRecord::Migration
    def self.up
        create_table :authentications do |t|
            t.integer   :user_id
            t.string    :provider
            t.string    :uid
            t.timestamps
        end
    end

    def self.down
        drop_table :authentications
    end
end

Authentication Model:

class Authentication < ActiveRecord::Base
    belongs_to :user
    validates :user_id, :uid, :provider, :presence => true
    validates_uniqueness_of :uid, :scope => :provider
end

Step 6:

And lastly, the authentications controller will be contain following code:

class AuthenticationsController < ApplicationController
  def create
    omniauth = request.env['omniauth.auth']
    authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])

    if authentication
      # User is already registered with application
      flash[:info] = 'Signed in successfully.'
      sign_in_and_redirect(authentication.user)
    elsif current_user
      # User is signed in but has not already authenticated with this social network
      current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
      current_user.apply_omniauth(omniauth)
      current_user.save

      flash[:info] = 'Authentication successful.'
      redirect_to home_url
    else
      # User is new to this application
      user = User.new
      user.authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
      user.apply_omniauth(omniauth)

      if user.save
        flash[:info] = 'User created and signed in successfully.'
        sign_in_and_redirect(user)
      else
        session[:omniauth] = omniauth.except('extra')
        redirect_to signup_path
      end
    end
  end

  def destroy
    @authentication = current_user.authentications.find(params[:id])
    @authentication.destroy
    flash[:notice] = 'Successfully destroyed authentication.'
    redirect_to authentications_url
  end

  private
  def sign_in_and_redirect(user)
    unless current_user
      user_session = UserSession.new(User.find_by_single_access_token(user.single_access_token))
      user_session.save
    end
    redirect_to home_path
  end
end

Step 7:

Update your user model and add following lines in it:

def apply_omniauth(omniauth)
  self.email = omniauth['user_info']['email']

  # Update user info fetching from social network
  case omniauth['provider']
  when 'facebook'
    # fetch extra user info from facebook
  when 'twitter'
    # fetch extra user info from twitter
  end
end

And here your go. You application is all ready for authentication with facebook and twitter. And yes, you can also register/signin using conventional email and password technique.

Interesting Fact:

The application that we’ve coded above not only supports one-time user registration using social networks. It also supports multiple social accounts for a single user. Yes, you read that right.

Remember, we have separate authentications table. So a user can have more than 1 way of  entering in the site. How to do this? Well, it’s as simple as reading this line. Once the user is signed in, you can redirect him again to the paths we used initially for signing him in. That is, you can write these lines again in your user’s homepage

<a href="/auth/twitter">Sign in with Facebook</a>
<a href="/auth/facebook">Sign in with Twitter</a>

and he’ll have multiple social accounts associated with same user. Of course, you can hide the links of those networks user has already connected with his account.

How is this done?

Go though the code of the authentications controller again. We’ve a nice little condition there:

elsif current_user
  # User is signed in but has not already authenticated with this social network
  current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
  current_user.apply_omniauth(omniauth)
  current_user.save

In case user is signed in, another authentication will be added for him and that’s all. Try to logout and login with newly added network. Yes, it meant to be as easy as 1,2,3…

Share the post if you liked it and it helped you.

Advertisements

30 thoughts on “Social Network Authentications with Omniauth and Authlogic

  1. Thanks for an excellent and very useful post! I am curious as to how you handle the creation of new User models when authentication occurs through a social network. Authlogic by default requires a password for a User model to pass validation and thus save. Do you redirect the users to a screen where they enter in a password combination or do you disable password validation for a User model?

    • Thanks for liking the post. Well, there can be two options as you’ve already mentioned. It all depends on the requirements of your application. A user may not like to enter a password after signing in with social network in most cases. So you can save user skipping the validations.

  2. Hello,

    Thanx for an excellent post ! I use authlogic. In my project i wan’t to include omniauth. But i’ve a problem with single_access_token : undefined method.

    Any idea ?

    Sorry bad english ^^.

    • Seems to be a problem with authlogic itself. Have you tried it before adding omniauth?

      Two basic checks could be:

      1. Do you have the column single_access_token in your users table?
      2. You might be accessing this token out of the scope.

      Please post the code snippet which is causing this exception and I’ll try to help you resolve this issue.

  3. Hi
    Thanks for a great tutorial!

    Could you please upload this to a git respository so I can look at your code in more detail please? I have followed your steps but I get the redirect to new_signup all the time.

    Is this because Im trying this localy or is it working but I dont understand that it does? 🙂

    Thanks in advance
    Philip

  4. undefined method `authentications’
    app/controllers/authentications_controller.rb:21:in `create’

    i followed your tutorial i am having this problem any ideas ?

  5. i am facing this problem where the intermediate page that appears during sigin in with fb is in a infinite loop. it never seems to go to my controller. this occurs in sigin and redirect method when i redirect to the home uri

  6. great work … i have a doubt.. i am building an app using omniauth. in my application omniauth[‘user_info’][’email’] returns NULL… please help me out..

  7. Won’t the user_id presence validation on the authentication fail if you’re using user.authentications.build? The authentication’s foreign key(user_id) won’t be assigned until after the authentication passes validation. And user.save won’t pass validation because it’s linked to an invalid authentication.

  8. Hi,
    This just isn’t working for me… It never creates a new user, a new authentications row or adds to an existing user…

    This is the console output:

    Authentication Load (0.4ms) SELECT `authentications`.* FROM `authentications` WHERE `authentications`.`provider` = ‘facebook’ AND `authentications`.`uid` = ‘xxxx’ LIMIT 1
    (0.1ms) BEGIN
    Authentication Exists (0.3ms) SELECT 1 FROM `authentications` WHERE (`authentications`.`uid` = BINARY ‘xxxx’ AND `authentications`.`provider` = ‘facebook’) LIMIT 1
    User Exists (0.2ms) SELECT 1 FROM `users` WHERE `users`.`email` = ‘xxx@xx.com.au’ LIMIT 1
    User Exists (0.2ms) SELECT 1 FROM `users` WHERE `users`.`persistence_token` = BINARY ‘8f7d95a23560ddfce14506a8d80c1a06ee5999f6d6862e06db766e018f3ed42f90208a543dadd89cadb5d43ec940316827f3df364d109cb1a50d3fa0aa043b25’ LIMIT 1
    User Exists (0.1ms) SELECT 1 FROM `users` WHERE `users`.`single_access_token` = BINARY ‘VOW8xh3kco6QNvQHhZA6’ LIMIT 1
    (0.1ms) ROLLBACK

  9. Nice tutorial but I’m not quite there yet.

    I get this when trying to click on the log in with twitter link:

    No route matches [GET] “/auth/twitter”

    I added these to my gemfile:

    gem ‘omniauth’
    gem ‘omniauth-twitter’
    gem ‘omniauth-facebook’

  10. Yes Tallal, I tried the given code and it fails exactly as andreas describes for the reason given by Dave – user_id presence validation fails for non existing users.

    Otherwise great overview.

  11. Validating :user_id presence broke me in rails 3.2. It seems that it causes the authentications to be invalid as they are being saved at the same time as the user. If using the build method this can safely be taken out. This would seem to me to be a rails bug though.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s