Rails Authentication with OAuth 2.0 and OmniAuth

Originally published at: http://www.sitepoint.com/rails-authentication-oauth-2-0-omniauth/

This entry is part 3 of 3 in the series Authentication in Rails

Authentication in Rails

This is the third article in the Authentication with Rails series. We’ve build a classic login/password authentication systems with features like remember me, reset password, confirm e-mail, and the like.

Today we are going to talk about authentication via social networks with the help of the OAuth 2 protocol. We will discuss OmniAuth and four of its strategies: Twitter, Facebook, Google+, and LinkedIn, allowing users to log in with any network they like. I will instruct you how to integrate each strategy, set it up, and handle errors.

Source code for this article is available on GitHub.

A working demo can be found at sitepoint-oauth2.herokuapp.com.

OAuth 2 and OmniAuth

OAuth 2 is an authorization protocol that enables a third-party applications to obtain limited access to an HTTP service. One of the main aspects of this protocol is the access token that is issued to the application. This token is used by the app to perform various actions on the user’s behalf. However, it can’t perform something that was not approved (for example, the user may only allow an app to fetch information about friends, but not post on the user’s wall).

This is cool because, under no circumstances should a third-party app access a user’s password. It only gets a special token that expires after some time (typically, users may also revoke access manually, as well) and may only be used o perform a list of approved tasks, called a scope.

How is a third-party app identified? Well, when talking about social networks, an app has to registered first. The developer provides the app’s URL, name, contact data, and other information that will be visible to the user. The authorization provider (Twitter or Facebook) then issues a key pair that uniquely identifies the app for the social network/provider.

Here is the simplistic overview of what happens when a user visits the app and tries to authenticate via a social network:

  • User clicks “Login” link
  • User is redirected to the social network’s website. The app’s data (client_id) are sent along for identification
  • User sees the app’s details (like name, logo, description, etc.) and which actions it would like to perform on his behalf (the scope). Think of it like you coming to the user and saying: “Hey, my name is Jack and I want to get the list of all your friends! If you agree, I’ll show you interesting statistics about them”
  • If the user does not trust this app, they just cancel the authorization
  • If the user trusts the app, the authorization is approved and the user is redirected back to the app (via callback URL). Information about the authenticated user and a generated token (sometimes with a secret key) is sent along.

There are numerous of services that support OAuth 2 authentication, so to standardize the process of creating authentication solutions OmniAuth was created by Intridea, Inc. It supports anything from OAuth 2 to LDAP. In this article, we will focus on omniauth-oauth2, an abstract OAuth 2 strategy. Basically it is used as a building block to easily craft authentication strategies on top of it. Here is the huge list of all strategies available for OmniAuth (it also includes those which are not related to OAuth 2).

Having a standard approach for strategies is great because you can integrate as many as you want without issue. Still, you have to remember that some social networks may return a different data about the authenticating user, so testing how your app works is absolutely necessary (I will show you some examples on this.)

If you have never integrated OAuth 2 into an app, this all might seem complicated, but don’t worry, soon it will become very clear! It is nice to learn by example, so let’s dive into the code and create something fancy.

Authentication in Rails

<< Devise Authentication in Depth
Continue reading this article on SitePoint

It is important to point out that using find_or_create_by in the User.from_omniauth method means that you will have no control over who has access to your application. That is, anyone with a valid twitter account will be able to generate a user object.

For many applications, that may be the desired behaviour, but there are many other instances where this is not the case. I do not think this should be the default behaviour described in this sort of tutorial. It should be described as an alternative. That is, you should describe the least dangerous configuration first and foremost, and then show alternative options.

I can’t fully agree on this, because the whole point of the app is to authenticate anyone and I do not consider it “dangerous”, this is just how the app works. We can say the same about traditional login-password authentication - anyone with a valid e-mail can authenticate. We understand, that if you need to restrict access somehow, additional steps have to be taken.

Of course, there may be times when you want to restrict access to a list of users (using invites, maybe), but I do not really think that readers need some additional explanation that with the current setup everyone can authenticate. Still, your concern is understandable and thank you for the feedback. I hope you find another piece of the article acceptable :smile:

I am just saying that it makes more sense to present solutions (quick and easy solutions if possible) to the common problems, not the safest solutions. Of course, if my solution presented some serious vulnerability I would pinpoint it or rather stay away from it, but that’s not the case here.

The description of your app is “a simple app allowing users to authenticate via one of the presented social networks”. However, it is both doing this and allowing user to effectively register via the same mechanism. Where in your piece does it state that “anyone with a valid e-mail can authenticate”.

You are conflating authentication with account creation in your example. That’s fine, but your article should at least clearly state that.

Nowhere because it does not.

We can say the same about traditional login-password authentication - anyone with a valid e-mail can authenticate.

I did not mean to say that this app allows authenticating via e-mail. I just mean that allowing any user to authenticate (register) via his social network profile (what was done in this app) presents no more security risks that allowing anyone with a valid e-mail to authenticate (register) in some other app.

Yeah, maybe I should have pinpoint the fact that creating an account means authenticating as well, but this is clearly seen from the controller’s code. Will keep that thing in mind.

Hi there. First of all, thanks for this very useful post.
I have a question about getting the keys from the providers. For now, I’ll be creating it with the localhost:3000 URL as shown in this guide, but what do I have to do once I decide to deploy my app into production?

Thanks!

I’ve already answered by e-mail, however others might have this question as well: yes, you’ll have to do this :smile:

I’m monitoring discussions for my articles, so you don’t have to duplicate questions by e-mail. Cheers!

Actually, I’m looking for a recommendation for this + access control lists as described. Eg Given users who authenticate, some users can see some things some users can see others. If a user authenticates and they have a role, they gain access to the views defined in said role.

This post is awesome, and exactly what I needed up to that point :slight_smile: I want to prevent the case of ‘anyone getting access to everything’ and can’t seem to find a good answer.

Thank you! Well, here is my article on CanCan, an authorization solution for Rails http://www.sitepoint.com/cancancan-rails-authorization-dance/ I am also going to cover Pundit in some time. :smile:

Hi i am getting the error when google+ callback occurs

Type Error no implicit conversion of String into Integer

Please help me to out this error.Thannks

Sorry Solved this.Browser shows above error but console was showing that Google+ Api is not enable for your project. So i enabled that one.And Working fine.Thanks for simple and nice article.

Yeah, that was a crucial step. Thank you!

I was just wondering, what would I have to change in this to allow the users in my app to be able to sign in sign in/link all of the social networks to my app? This is so that when they press the share button on a post they will be able to share it to all of their social networks at one time

Well, that would be a bit more complex setup and unfortunately you’ll have to experiment yourself. The idea is to allow multiple authentications per user and just check that if he already logged in and logging in via another social network, just update his data in the appropriate field.

Hey, thank you for this nice tutorial. Now I’ve completed only up-to Twitter integration, and struck with a problem.

I run rails server inside Virtual Box with Ubuntu server guest. Hence to access rails app from host, I force rails server with this command “rails s -p 80 -b 192.168.x.xx”, So that in host browser entering just IP address will open my rails app and its working fine.
The problem is
In my Twitter app (https://apps.twitter.com/) I have given Website as “http://192.168.x.xx” and Callback URL as “http://192.168.x.xx/auth/twitter/callback”, but when I click twitter link from my app, Its showing error “OAuth::Unauthorized”. After I googled this issue, I found from StackOverflow that it happens when callback URL is wrongly specified. In this tutorial it is suggested to use “http://localhost:3000” which is not possible in my situation.

Any ideas?
Thank you.

Hi! What do these “x” stand for?

Hello bodrovis, I meant to show it was a dynamic IP and it keeps changing. “x” is a variable. Some times its 192.168.1.15, 192.168.1.18, 192.168.1.14 etc and it keeps changing each time I start Virtualbox. But I made sure that the current generated IP is same as the IP I have given in the twitter. And today I’ve to change the IP in twitter according to newly generated IP inside Ubuntu guest. What else I am missing?

Thank you.

Wait, if you are developing on your Ubuntu machine and interacting with your app from the same machine, you’ll have to provide 127.0.0.1 as an address.

Hey sorry, the problem was with this line “provider :twitter, ENV[‘TWITTER_KEY’], ENV[‘TWITTER_SECRET’]”.

As you have stated to copy paste Twitter key and secret, I did exactly same. But later found out that ENV[…] will search for environment variable set in the system. I found the solution after integrating Facebook login, with the error fb_id is not found. The solution was to either set environment variable for TWITTER_KEY and “T…_SECRET” or remove them and copy paste the key and secret as a string, withing double quotes (“sdfjk23424…”). I choose string method.

How ever now I have new problems now.

  1. Using Twitter I can login but I can’t logout. For some reason delete request is not being processed. I’m sure of integrating all code to app controller and user controller. Still working on it.

  2. And Facebook authentication happened up-to accepting app permissions. But soon after I accept it is showing me the error " :
    {“error”:{“message”:“Error validating client secret.”,“type”:“OAuthException”,“code”:1,“fbtrace_id”:“FNsnm7HwnE3”}}".
    Still digging on it.

  3. Google+ requires some thing other than basic IP addresses (192.168…) like http://localhost. So its not going to happen anytime soon. I have to map my rails server to no-ip free domain name.

  4. LinkedIn yet have to check. Anyways thank you for your support. I’m glad if you know solution to any of the above.