How to Use Omniauth to Authenticate your Users

How to Use Omniauth to Authenticate your Users

Tutorial Details
  • Topic: Omniauth, Ruby on Rails
  • Difficulty: Beginner/Intermediate
  • Estimated Completion Time: 30 minutes

I hate signing up for websites. I’ve already signed up for so many, using different usernames, that going back to one of them and trying to remember my credentials is sometimes impossible. These days, most sites have begun offering alternative ways to sign up, by allowing you to use your Facebook, Twitter or even your Google account. Creating such an integration sometimes feels like a long and arduous task. But fear not, Omniauth is here to help.

Omniauth allows you to easily integrate more than sixty authentication providers, including Facebook, Google, Twitter and GitHub. In this tutorial, I’m going to explain how to integrate these authentication providers into your app.


Step 1: Preparing your Application

Let’s create a new Rails application and add the necessary gems. I’m going to assume you’ve already installed Ruby and Ruby on Rails 3.1 using RubyGems.

rails new omniauth-tutorial

Now open your Gemfile and reference the omniauth gem.

gem 'omniauth'

Next, per usual, run the bundle install command to install the gem.


Step 2: Creating a Provider

In order to add a provider to Omniauth, you will need to sign up as a developer on the provider’s site. Once you’ve signed up, you’ll be given two strings (sort of like a username and a password), that needs to be passed on to Omniauth. If you’re using an OpenID provider, then all you need is the OpenID URL.

If you want to use Facebook authentication, head over to developers.facebook.com/apps and click on “Create New App”.

Facebook New App

Fill in all necessary information, and once finished, copy your App’s ID and Secret.

Facebook Secret

Configuring Twitter is a bit more complicated on a development machine, since they don’t allow you to use “localhost” as a domain for callbacks. Configuring your development environment for this kind of thing is outside of the scope of this tutorial, however, I recommend you use Pow if you’re on a Mac.


Step 3: Add your Providers to the App

Create a new file under config/initializers called omniauth.rb. We’re going to configure our authentication providers through this file.

Paste the following code into the file we created earlier:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, YOUR_APP_ID, YOUR_APP_SECRET
end

This is honestly all the configuration you need to get this going. The rest is taken care of by Omniauth, as we’re going to find in the next step.


Step 4: Creating the Login Page

Let’s create our sessions controller. Run the following code in your terminal to create a new sessions controller, and the new, create, and failure actions.

rails generate controller sessions new create failure

Next, open your config/routes.rb file and add this:

get   '/login', :to => 'sessions#new', :as => :login
match '/auth/:provider/callback', :to => 'sessions#create'
match '/auth/failure', :to => 'sessions#failure'

Let’s break this down:

  • The first line is used to create a simple login form where the user will see a simple “Connect with Facebook” link.
  • The second line is to catch the provider’s callback. After a user authorizes your app, the provider redirects the user to this url, so we can make use of their data.
  • The last one will be used when there’s a problem, or if the user didn’t authorize our application.

Make sure you delete the routes that were created automatically when you ran the rails generate command. They aren’t necessary for our little project.

Open your app/controllers/sessions_controller.rb file and write the create method, like so:

def create
  auth_hash = request.env['omniauth.auth']

  render :text => auth_hash.inspect
end

This is used to make sure everything is working. Point your browser to localhost:3000/auth/facebook and you’ll be redirected to Facebook so you can authorize your app (pretty cool huh?). Authorize it, and you will be redirected back to your app and see a hash with some information. In between will be your name, your Facebook user id, and your email, among other things.


Step 5: Creating the User Model

The next step is to create a user model so users may sign up using their Facebook accounts. In the Rails console (rails console), create the new model.

rails generate model User name:string email:string

For now, our user model will only have a name and an email. With that out of the way, we need a way to recognize the user the next time they log in. Keep in mind that we don’t have any fields on our user’s model for this purpose.

The idea behind an application like the one we are trying to build is that a user can choose between using Facebook or Twitter (or any other provider) to sign up, so we need another model to store that information. Let’s create it:

rails generate model Authorization provider:string uid:string user_id:integer

A user will have one or more authorizations, and when someone tries to login using a provider, we simply look at the authorizations within the database and look for one which matches the uid and provider fields. This way, we also enable users to have many providers, so they can later login using Facebook, or Twitter, or any other provider they have configured!

Add the following code to your app/models/user.rb file:

has_many :authorizations
validates :name, :email, :presence => true

This specifies that a user may have multiple authorizations, and that the name and email fields in the database are required.

Next, to your app/models/authorization.rb file, add:

belongs_to :user
validates :provider, :uid, :presence => true

Within this model, we designate that each authorization is bound to a specific user. We also set some validation as well.


Step 6: Adding a Bit of Logic to our Sessions Controller

Let’s add some code to our sessions controller so that it logs a user in or signs them up, depending on the case. Open app/controllers/sessions_controller.rb and modify the create method, like so:

def create
  auth_hash = request.env['omniauth.auth']

  @authorization = Authorization.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
  if @authorization
    render :text => "Welcome back #{@authorization.user.name}! You have already signed up."
  else
    user = User.new :name => auth_hash["user_info"]["name"], :email => auth_hash["user_info"]["email"]
    user.authorizations.build :provider => auth_hash["provider"], :uid => auth_hash["uid"]
    user.save

    render :text => "Hi #{user.name}! You've signed up."
  end
end

This code clearly needs some refactoring, but we’ll deal with that later. Let’s review it first:

  • We check whether an authorization exists for that provider and that uid. If one exists, we welcome our user back.
  • If no authorization exists, we sign the user up. We create a new user with the name and email that the provider (Facebook in this case) gives us, and we associate an authorization with the provider and the uid we’re given.

Give it a test! Go to localhost:3000/auth/facebook and you should see “You’ve signed up”. If you refresh the page, you should now see “Welcome back”.


Step 7: Enabling Multiple Providers

The ideal scenario would be to allow a user to sign up using one provider, and later add another provider so he can have multiple options to login with. Our app doesn’t allow that for now. We need to refactor our code a bit. Change your sessions_controlller.rb’s create method to look like this:

def create
  auth_hash = request.env['omniauth.auth']

  if session[:user_id]
    # Means our user is signed in. Add the authorization to the user
    User.find(session[:user_id]).add_provider(auth_hash)

    render :text => "You can now login using #{auth_hash["provider"].capitalize} too!"
  else
    # Log him in or sign him up
    auth = Authorization.find_or_create(auth_hash)

    # Create the session
    session[:user_id] = auth.user.id

    render :text => "Welcome #{auth.user.name}!"
  end
end

Let’s review this:

  • If the user is already logged in, we’re going to add the provider they’re using to their account.
  • If they’re not logged in, we’re going to try and find a user with that provider, or create a new one if it’s necessary.

In order for the above code to work, we need to add some methods to our User and Authorization models. Open user.rb and add the following method:

def add_provider(auth_hash)
  # Check if the provider already exists, so we don't add it twice
  unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
    Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
  end
end

If the user doesn’t already have this provider associated with their account, we’ll go ahead and add it — simple. Now, add this method to your authorization.rb file:

def self.find_or_create(auth_hash)
  unless auth = find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
    user = User.create :name => auth_hash["user_info"]["name"], :email => auth_hash["user_info"]["email"]
    auth = create :user => user, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
  end

  auth
end

In the code above, we attempt to find an authorization that matches the request, and if unsuccessful, we create a new user.

If you want to try this out locally, you’ll need a second authentication provider. You could use Twitter’s OAuth system, but, as I pointed out before, you’re going to need to use a different approach, since Twitter doesn’t allow using “localhost” as the callback URL’s domain (at least it doesn’t work for me). You could also try hosting your code on Heroku, which is perfect for a simple site like the one we’re creating.


Step 8: Some Extra Tweaks

Lastly, we need to, of course, allow users to log out. Add this piece of code to your sessions controller:

def destroy
  session[:user_id] = nil
  render :text => "You've logged out!"
end

We also need to create the applicable route (in routes.rb).

get '/logout', :to => 'sessions#destroy'

It’s as simple as that! If you browse to localhost:3000/logout, your session should be cleared, and you’ll be logged out. This will make it easier to try multiple accounts and providers. We also need to add a message that displays when users deny access to our app. If you remember, we added this route near the beginning of the tutorial. Now, we only need to add the method in the sessions controller:

def failure
  render :text => "Sorry, but you didn't allow access to our app!"
end

And last but not least, create the login page, where the user can click on the “Connect With Facebook” link. Open app/views/sessions/new.html.erb and add:

<%= link_to "Connect With Facebook", "/auth/facebook" %>

If you go to localhost:3000/login you’ll see a link that will redirect you to the Facebook authentication page.


Conclusion

I hope this article has provided you with a brief example of how Omniauth works. It’s a considerably powerful gem, and allows you to create websites that don’t require users to sign up, which is always a plus! You can learn about Omniauth on GitHub.

Let me us know if you have any questions!

Add Comment

Discussion 48 Comments

  1. Dru Wynings says:

    To get Twitter auth to work locally, you have to set the callback url as http://127.0.0.1:3000/ instead of http://localhost:3000/

  2. Andrew says:

    Is there any library like this for PHP, preferably not requiring another framework life CodeIgniter?

  3. mekele says:

    Can you recommend some similar library for PHP?

  4. Michael says:

    Anyone know of a similar library for PHP? This looks great, but don’t have the ability to run Ruby right now… :-(

  5. I may look at forking a PHP version of this (if there already isn’t one) as omniauth seems like a decent project that would be good to contribute to and hopefully end the days of thousands of different logins!

  6. alinouman says:

    Can you make this tutorial for php?

  7. Daniel Petrie says:

    For those looking for a similar library for PHP – Check out NinjAuth which is currently a Fuel package. You can find the repo here https://github.com/philsturgeon/fuel-ninjauth and description here http://philsturgeon.co.uk/blog/2011/09/ninjauth-social-integration-php

  8. vaff says:

    How about a python fork :D?

  9. daGrevis says:

    Are there any alternatives for PHP?

  10. kankuro says:

    @all who ask for php alernatives, we better create this one in php version.. coz, this feature is great :D

  11. The idea of an OmniAuthentication helper is really great. Unfortunately I’m not a Ruby developer. I didn’t understand much of your code and explanation. But thanks for the link to GitHub. I’ll start on my own using PHP and .NET.

    Thanks.

  12. excellent tutorial…. would try to implement it this weekend on one of the project we’re working on.

    Thanks

  13. Ian Murray says:
    Author

    Unfortunately for all the people asking for a PHP equivalent, I don’t know if there is any. :( I couldn’t find one doing a quick google search either.

  14. daGrevis says:

    Maybe someone could port Omniauth to PHP and put in on GitHub? Together we could fix bugs and make the world better… :)

  15. sirfilip says:

    Nice tut mate.
    @all there are screencasts covering Omniauth at http://railscasts.com just search for omniauth.

  16. Charles says:

    Thanks for your tutorial. I’ve been working on a project and I’ve been meaning to use this gem and you provide a great example to help me get started on my Rails project.

  17. For all you PHP programmers, this could be interesting: http://hybridauth.sourceforge.net/

  18. Daniel Kehoe says:

    Have you seen this?

    https://github.com/RailsApps/rails3-mongoid-omniauth

    It’s a complete Rails 3.1 example app with tutorial that shows how to use OmniAuth.

    The app is open source with an issue tracker and a number of contributors on GitHub.

    The rails3-mongoid-omniauth example app has a tutorial that includes a lot of detail.

    Might be useful to compare implementations.

  19. Michael says:

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

    :(

  20. Bobby says:

    This is almost a complete duplicate of the RailsCasts.org OmniAuth tutorial, except they used Twitter. : /

  21. Peyton says:

    you can get twitter to work local by going to a url shortening service and shortening localhost:3000 to something like goo.gl/1234 and using the shortened url as the callback.

  22. moncler says:

    This is almost a complete duplicate of the RailsCasts.org OmniAuth tutorial, except they used Twitter.

  23. Dean says:

    Will this work with/alongside Devise?

  24. Zack says:

    The gem is somehow blocking my ability to generate controllers (not just your controllers you use as an example, i mean I couldn’t even run “rails generate controller test”). If this isn’t working at Step 2 and I don’t have some imperative reason to know how to do this, I’m afraid to say you’ve already lost me lol.

  25. moncler says:

    perative reason to know how

  26. Vezu says:

    I am not sure why but when i try to use this Gem with Devise i get an error. Has anyone tried it?

  27. Samuel says:

    I faced some problem when adding the gem ‘omniauth’ in my gemfile, but while i added gem gem ‘omniauth-facebook’ it worked for me.. In rails 3.0.9 app
    Thanks and great work and articles..Keep it up

  28. Paddy says:

    Nice tutorial, and a good complements to existing railscasts :)

    Just have one minor issue / question, once my Twitter credentials have been saved to the database, I keep being asked if I want to authorize the application on login. Don’t think this is a regular behavior, may it be due to the fact I’m testing this locally ?

  29. Shan says:

    How do you handle expired Facebook tokens? Facebook tokens expire in ~2 hours. So if the user is logged into the app and then tries to post to Facebook after the token is expired, how do you handle that exception?

    • yogesh says:

      i have the same problem can anyone help
      all the codes i have search is in PHP . and i am
      with jsp javascript .
      How do you handle expired Facebook tokens? Facebook tokens expire in ~2 hours. So if the user is logged into the app and then tries to post to Facebook after the token is expired, how do you handle that exception?

  30. Bryan says:

    This gets very broken after OmniAuth 1.0

  31. nagaraju says:

    I am getting this error

    NoMethodError in SessionsController#create

    undefined method `[]‘ for nil:NilClass

  32. madhavi says:

    Even though i wrote the destroy method for clearing the session when user logout its not logging out to signup for another user..any help?

  33. htasnim says:

    I am getting this error when I go to localhost:3000/login and login with my Facebook account:

    OpenSSL::SSL::SSLError
    SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

  34. Sterling says:

    I have a question about having a multistep sign in process. I want to ask my user to enter their email and password in after clicking ‘Sign up with Facebook’ so they have an alternative way of signing in. How can I add this extra page without exposing their uid to another form that gets submitted?

  35. Nicholas says:

    Hi, i getting a nill object when it successfull callback to my website..How to solve this problem??

  36. bala says:

    Ian, what happens when the user closes the browser and comes back again. Does he have to authorize/allow access again?.

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.