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”.
Fill in all necessary information, and once finished, copy your App’s ID and 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 thatuid
. 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 theuid
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!
Comments