These days, I have been playing a lot with Oauth and its RoR implementation, oauth-plugin. Its documentation is a bit short, so here is a tutorial to show how to use it, both in provider and consumer mode. And we will even make them communicate with each other.
We will now build an Oauth provider using oauth-plugin for authorization and Devise for authentication. And we will add a controller protected by Oauth.
Starting up
A few instructions to create the application. You won’t need an explanation for this:
rails new provider cd provider
Put this in your Gemfile:
source 'http://rubygems.org' gem 'rails', '3.0.7' gem 'sqlite3' gem 'devise' gem "oauth-plugin", ">= 0.4.0.pre1"
And a few more commands:
bundle install rails generate devise:install rails generate devise User rake db:migrate rails generate controller welcome index rm public/index.html
And don’t forget ‘root :to => “welcome#index”‘ in config/routes.rb.
Create the provider
rails generate oauth_provider oauth rake db:migrate
You could put something else than “oauth” as parameter, but for the moment, the generator has some bugs (it always generate the class OauthController, but with a different name). I’ll check more recent versions of the code.
Now, modify config/application.rb and add:
require 'oauth/rack/oauth_filter' config.middleware.use OAuth::Rack::OAuthFilter
Put in app/models/user.rb:
has_many :client_applications has_many :tokens, :class_name=>"OauthToken",:order=>"authorized_at desc",:include=>[:client_application]
Put in app/controllers/oauth_controller.rb:
alias :logged_in? :user_signed_in? alias :login_required :authenticate_user!
and uncomment authenticate_user.
Put in app/controllers/oauth_clients_controller.rb:
alias :login_required :authenticate_user!
And now some data
Create a new controller:
rails generate controller data index
And now, edit your controller:
class DataController < ApplicationController before_filterauth_required def index @data = { "coincoin" => "o< o<" } respond_to do |format| format.json { render :json => @data } end end end
UPDATE
I discovered a few bugs in this tutorial, so here are the fixes.
oauth-plugin needs the function current_user=, so add this to your ApplicationController:
def current_user=(user) current_user = user end
Next, to handle revocation, you need to add this to config/routes.rb:
post 'oauth/revoke'
And at last, you need to fix the rack filter. The current code doesn’t verify the token validity, and lets revoked tokens access your data.
You have to modify lib/oauth/rack/oauth_filter.rb in the oauth-plugin gem folder.
Replace the line 46:
oauth_token = client_application.tokens.first(:conditions=>{:token => request_proxy.token})
by
oauth_token = ClientApplication.find_token(request_proxy.token)
And that’s it!
You now have a working provider. OauthController handles all the communication with the consumers. OauthClientsController manages the registration of new consumers. They both have customizable views: oauth for the authorization part (for users) and oauth clients for the consumers. And you just need the oauth_required filter to manage access to your data.
And now, you can go to /users/sign_up, then /users/sign_in, then /oauth_clients to register a new client application. You just need to give a name for your application, your URL, and a callback URL.
In the next post, we will build a consumer, and this consumer will access the provider’s data.
Nice post, just a couple points:
Shouldn’t the line
alias :login_required :authenticate_user
actually be (not the exclamation mark):
alias :login_required :authenticate_user!
The line that needs to be edited in lib/oauth/rack/oauth_filter.rb is in the oauth-plugin gem folder, not that of the local app.
Gazler.
You’re right, I have fixed this. Thanks!
What about authenticate_user vs. authenticate_user! ?
Your “update” saved me hours upon hours on work. When I take control of the world I shall erect a statue in your honour.
I spent a few hours on these bugs, so I’m happy I spared you the effort
Great write-up. For me though, it had to be:
def current_user=(user)
sign_in(user)
end
Just in case anybody else runs into the same issue with oauth-plugin/devise.
Ulf
Hi i followed the above steps and when i try to generate controllers as above
rails generate controller data index
i am getting the following error
/home/lenovo-10/Desktop/digital/config/application.rb:4:in `require’: no such file to load — oauth/rake/oauth_filter (LoadError)
from /home/lenovo-10/Desktop/digital/config/application.rb:4:in `’
from /home/lenovo-10/.rvm/gems/ruby-1.9.2-p290@rails3/gems/railties-3.1.0/lib/rails/commands.rb:21:in `require’
from /home/lenovo-10/.rvm/gems/ruby-1.9.2-p290@rails3/gems/railties-3.1.0/lib/rails/commands.rb:21:in `’
from script/rails:6:in `require’
from script/rails:6:in `’
what would be the reason and how can i come out from it.
You are missing some gems. Verify that oauth_filter is available for ruby 1.9.2
I followed this tutorial exactly, but when I try to authenticate with my consumer, the provider blows up on OauthController#authorize. Stack trace says 0 for 2 arguments for authenticate_user on line 160 of lib/oauth/controllers/provider_controller.rb. Finding that method is easy, however, finding what’s calling it isn’t. I’ve tried debugging it, but rails seems to jump right over my debugger, and adding a breakpoint won’t even work. I’m sure I’m doing something stupid, but I have no idea what, and I’ve been staring at this for hours. Any help would certainly be appreciated.
Hello,
the wrong function wasn’t there, that’s why your breakpoint didn’t halt the app. There was a typo in the tutorial
In app/controllers/oauth_controller.rb, you must replace “alias :login_required :authenticate_user” by “alias :login_required :authenticate_user!”
I updated the tutorial. Sorry for the mistake.
Argh! Thank you so much! I should have looked closer at that, since some other commenters mentioned problems with that method and it wasn’t really clear which one it was supposed to be.
I have a problem with this tutorial. When it calls ‘data / index’ getting the error: Invalid OAuth Request. When the callback is invoked in a consumer app.
error:
ActiveRecord:: RecordInvalid in OAuth consumersController # callback
Validation failed: User can not be blank
app/controllers/oauth_consumers_controller.rb:16:in `callback’
The application does not copy the users.
I do not know where I made a mistake.
my gems:
rails 3.0.7
devise 1.5.0
ruby 1.9.2p0
I don’t really get what your problem is. Are you expecting that the consumer app create a user from the call to the provider? This tutorial is about getting access to another application from one you’re already using (ex: giving your Linkedin account an access to your twitter account). If you want to log in using Oauth, there’s a much simpler solution: the Omniauth gem.
Thank you for your quick response
I wanted to get something like that.
One application that contains users. Another application is using it.
For example, a user logs in to the application provider and has access to other applications through the provider.
One login -> multiple applications -> different roles in applications.
I use ‘Devise’ and for this I need a solution. I have not found how to create an provider application in the ‘Omniauth’.
I hoped that OAuth is what I’m looking for
That is not really the definition of a provider for Oauth.
Oauth is an authorization protocol: the provider is holding some data, and the consumer wants to access it, and Oauth is used to manage the authorization of consumers.
The Oauth-plugin gem can act as a provider (see this tutorial) or as a consumer (see the next tutorial).
Devise is a completely different thing: it is an authentication system. It is used to manage your users and their credentials.
Omniauth is also an authentication system, but it uses Oauth in consumer mode to perform the authentication: it tries to get access to your data in another application, and if the access is granted, it means that you were authenticated by the provider. It delegates the authentication to the provider. At the same time, it receives Oauth tokens that can be used to access the user’s data.
Now, could you explain a bit more your use case?
I have written an application in which I use Devise. I want to write a second application also uses Devise. I do not want to require users to log in to both applications. I want to log on only once and can use both applications. Additionally, in the second application users will have different roles, allowing access to different parts of the application.
Users can not register themselves, are registered by the administrator, he can give them access to both applications or only one
For example, if I log on to gmail I have access to all Google Apps. I do not have to login to each separately.
I’m looking for something that will help me in creating such a connection between applications.
One application keeps users and other log on to it.
I hope I explained it well, but I’m not sure if it will work with OAuth or OmniAuth
This is another system:, called single sign on.
If I understand correctly, you want two applications with different sets of users (users of the 1st, users of the 2nd, users of both). And an user of both applications must be able to log in an application, and be automatically logged in the other.
There are multiple ways to do that.
The first is to use a 3rd app, an external user manager (with Oauth or SAML), to authenticate the user. On the first login on one app, the user is redirected to the user manager, enters login and password, and is redirected to the app. If he wants to log on the 2nd app, he is redirected to the user manager, but doesn’t need to enter his credentials, and is automatically authenticated.
The second, which may be simpler in your case, would be to share the database and the users table, and have the session cookies work on both sites (by putting them in subdomains).
Thank you very much for your suggestions. I will try the second method
Maybe I will succeed
Hi,
I am using following combinations for development
Rails 3.1.2
Oauth 0.4.4
Oauth-plugin 0.4.0.rc2
Devise
I have created a custom provider and consumer
Passing case
I am logged in to consumer and I am trying to login to the consumer with provider, I am
getting redirected to the provider, asking for the authorization form, I authorize and come back.
Failing case
I am *not* logged in to consumer and I am trying to login to the consumer with provider, I am
getting redirected to the provider, asking for the authorization form
and its redirecting me to page saying Validation failed, user can’t be blank.
I have also added code in application controller for
def current_user=(user)
current_user = user
end
Please let me know in case I am missing something, as the access token generated is not able to identify the correct user who previously has created one.
See the link posted by Don Pottinger in the comments: https://github.com/pelle/oauth-plugin/issues/96
It must be the same issue.
Does it provide a Three-legged method (like Twitter for desktop applications, with a pin-code)?
I’m trying to use it with this but still can’t make it work?
AFAIK Twitter uses two-legged authorization. The method described here is for three-legged. What is your use case?
I am having trouble with the last part of your bug fix. The part where I have to replace a line in a file in the gem’s folder. I cannot find this folder, or a file that is named oauth_filter.rb.
Could you tell me where I can locate that file?
Thank you!
Rails + oauth-plugin + mongodb part 1: Provider | Ruby on Rails freelancer
Thanks Géal, I just have posts for mongoid based on your guide here: http://giangnd.wordpress.com/2012/05/15/rails-oauth-plugin-mongodb-part-1-provider/
Bonjour Géal!
Thanks a lot for this tutorial. It was very useful and easy to follow, and saved me a lot of time.
I’m a Rails newbie and am using Rails 3.2.3, and had to do the following additional changes to the code generated by oauth-plugin for it to work for OAuth 2.0. I’m posting it here to help others who might face the same problems.
Problem 1. The error_messages_for method isn’t available anymore
$ vi app/models/client_application.rb
-> edit to use @client_application.errors instead
Problem 2. Mandatory attr_accessible calls are missing.
I ran into the problem in these 3 models.
$ vi app/models/client_application.rb
-> Add: attr_accessible :name, :url, :callback_url, :support_url
$ vi app/models/oauth2_verifier.rb
-> Add: attr_accessible :client_application, :user, :scope, :callback_url
$ vi app/models/oauth2_token.rb
-> Add: attr_accessible :user, :client_application, :scope
Problem 3. Error in the OAuth 2 Authorize view.
# undefined method `client_application’ for nil:NilClass
$ vi app/views/oauth/oauth2_authorize.html.erb
-> Change all @token.client_application to @client_application
That’s all.
Thank you for your help!
I was still having trouble with the error messages, they wasnt appearing in my browser, so, my work around was:
to catch the error msg!
salut!
this is really great. I’m not using devise, so I’ll have to look into getting this working with my authentication system properly. By the way, I’m thinking about using this with grape for api authentication. Do you think that’s a good idea?
That’s a good idea. Once you have a provider up and running, it shouldn’t be too hard to include OAuth token verification in your API.