Rails and oauth-plugin part 1: the provider
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_filter :oauth_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!
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