I'm using OmniAuth gem along with the specific provider gems for FB, Linkedin and G+, both for login, registration and information retrieval. I want to offer further integration with other API's in this case with Upwork's api, that uses OAuth 1.
I've set the App with Upwork and have working key and secret. I've set the loader to load my custom strategy (since it's not a gem) and it loads. I've set the provider to pass the key and secret which are stored in an env file.
All of that seems to be working now, after many hours into it.
I tried reading through the sparse information contained in OmniAuth's strategy guide, along with OAuth wiki, and looked into the gem files of other providers. I ended up copying a bit of the code I thought would be enough to work through this, at least, for login but I'm messing something up.
Whenever I go to the callback path for upwork, set automatically by omniauth I get an error.
Started GET "/auth/upwork" for ::1 at 2015-07-29 00:08:12 +0800
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
I, [2015-07-29T00:08:12.169605 #24517] INFO -- omniauth: (upwork) Request phase initiated.
OAuth::Unauthorized (405 Method Not Allowed):
lib/omniauth/strategies/upwork.rb:18:in `request_phase'
Rendered /Users/mnussbaumer/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (5.1ms)
By the documentation this seems to be when I either try a GET to a POST only, or a POST to a GET only endpoint.
In Upwork's API reference they explicitly say that:
Get request token
Endpoint
POST /api/auth/v1/oauth/token/request
My strategy is currently as this:
require 'json'
require 'omniauth-oauth'
module OmniAuth
module Strategies
class Upwork < OmniAuth::Strategies::OAuth
option :client_options, {
:site => "https://www.upwork.com/api",
:request_token_path => "/api/auth/v1/oauth/token/request",
:authorize_url => "/services/api/auth",
:access_token_path => "api/auth/v1/oauth/token/access",
}
uid { request.params['user_id'] }
def request_phase
request_token = consumer.get_request_token(:oauth_callback => callback_url)
session['oauth'] ||= {}
session['oauth'][name.to_s] = {'callback_confirmed' => request_token.callback_confirmed?, 'request_token' => request_token.token, 'request_secret' => request_token.secret}
if request_token.callback_confirmed?
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_consumer_key => consumer.key))
else
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_callback => callback_url, :oauth_consumer_key => consumer.key))
end
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
end
def raw_info
#raw_info ||= JSON.load(access_token.get('/me.json')).body
end
end
end
end
I tried changing "consumer.get_request_token" to "consumer.post_request_token" but I think that has nothing to do with it.
The request_phase was ripped off of a gem I found and the JSON.load from a different one. I thought it would work with only these 2 but it seems not. I'm learning slowly how to use all this and would like to build first a usable strategy and then provide it as a public gem for omniauth.
UpWork has an API documentation, and they even have a gem for ruby, but I would like to use OmniAuth for everything, plus, I'll need to figure out other API's in the future so I would like to know how to do this well.
https://developers.upwork.com/?lang=ruby#authentication_oauth-10
Anybody can help with this? Or with creating an OmniAuth gem for Upwork.
Thanks!
(edited to change the error - now it's much thinner output but it's the same error)
The request_phase method is actually a method that belongs to omniauth-oauth which you required on top of the upwork.rb and your class Upwork inherits it (OmniAuth::Strategies::OAuth). you don't have to override it.
Related
Currently I am working on rails 4 project, and now I have to link / connect another application (not sso but for accessing API's) say example.com. (Note: example.com uses 3-legged oauth security architecture)
After searching found that I have to implement omniouth strategy.
For this I have refereed this link. As per Strategy-Contribution-Guide I am able to complete setup and request Phase, You can find my sample code here.
require 'multi_json'
require 'omniauth/strategies/oauth2'
require 'uri'
module OmniAuth
module Strategies
class MyAppStrategy < OmniAuth::Strategies::OAuth2
option :name, 'my_app_strategy'
option :client_options, {
site: site_url,
authorize_url: authorize_url,
request_url: request_url,
token_url: token_url,
token_method: :post,
header: { Accept: accept_header }
}
option :headers, { Accept: accept_header }
option :provider_ignores_state, true
def consumer
binding.pry
::OAuth::Consumer.new(options.client_id, options.client_secret, options.client_options)
end
def request_phase # rubocop:disable MethodLength
binding.pry
request_token = consumer.get_request_token({:oauth_callback => callback_url}, options.request_params)
session["oauth"] ||= {}
session["oauth"][name.to_s] = {"callback_confirmed" => request_token.callback_confirmed?, "request_token" => request_token.token, "request_secret" => request_token.secret}
if request_token.callback_confirmed?
redirect request_token.authorize_url(options[:authorize_params])
else
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_callback => callback_url))
end
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
end
def callback_phase # rubocop:disable MethodLength
fail(OmniAuth::NoSessionError, "Session Expired") if session["oauth"].nil?
request_token = ::OAuth::RequestToken.new(consumer, session["oauth"][name.to_s].delete("request_token"), session["oauth"][name.to_s].delete("request_secret"))
opts = {}
if session["oauth"][name.to_s]["callback_confirmed"]
opts[:oauth_verifier] = request["oauth_verifier"]
else
opts[:oauth_callback] = 'http://localhost:3000/auth/callback' #callback_url
end
#access_token = request_token.get_access_token(opts)
super
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
rescue ::OAuth::Unauthorized => e
fail!(:invalid_credentials, e)
rescue ::OmniAuth::NoSessionError => e
fail!(:session_expired, e)
end
def custom_build_access_token
binding.pry
verifier = request["oauth_verifier"]
client.auth_code.get_token(verifier, get_token_options(callback_url), deep_symbolize(options.auth_token_params))
end
alias_method :build_access_token, :custom_build_access_token
def raw_info
binding.pry
#raw_info ||= access_token.get('users/me').parsed || {}
end
private
def callback_url
options[:redirect_uri] || (full_host + script_name + callback_path)
end
def get_token_options(redirect_uri)
{ :redirect_uri => redirect_uri }.merge(token_params.to_hash(:symbolize_keys => true))
end
end
end
end
I am able redirect to example.com, also after login I am able to return to my callback_phase (you will ask how did you know, so answer is I have added binding.pry in callback_phase method for checking the flow).
But after executing the strategy I am getting following error
ERROR -- omniauth: (my_app_strategy) Authentication failure! invalid_credentials: OAuth2::Error.
After debugging found that I am getting this error for the super call (from callback_phase method).
First I though may be there are some credentials issue but I am able fetch access token using following (which is executing before the super call)
#access_token = request_token.get_access_token(opts)
Also for more information I am getting error for build_access_token which is the oauth2 method
You can refer this link for more info (just search the build_access_token on the page).
EDIT - 1
After debugging found that getting this issue from the request method.
(While making the faraday request). Here is the code snippet
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
yield(req) if block_given?
end
Here is my faraday request
#<struct Faraday::Request method=:post, path="example.com/oauth/access_token", params={}, headers={"User-Agent"=>"Faraday v0.9.2", "Content-Type"=>"application/x-www-form-urlencoded"}, body={"grant_type"=>"authorization_code", "code"=>"aPexxxvUg", "client_id"=>"xxxxxur303GXEch7QK9k", "client_secret"=>"xxxxxxcad97b3d252e2bcdd393a", :redirect_uri=>"http://localhost:3000/auth/my_app_strategy/callback"}, options=#<Faraday::RequestOptions (empty)>>
In response I am getting following error message
HTTP Status 400 - Inadequate OAuth consumer credentials.
So can any one help to fix this issue?
Is there any other way to store the access token so that I can utilize this for communication purpose.
Thanks
First of all, I wan to make clear how Oauth2 works:
Oauth2, the protocol says:
You redirect the user to the provider sign in endpoint adding some required parameters (Ejm: PROVIDER/public/oauth?redirect_uri=MYWEB/oauthDemo&
response_type=code&client_id=ABCDE). Sometimes there is also a scope/permission/resource parameter that indicates whats your purpose.
-> Then the users signs in and is redirected to your endpoint MYWEB/public/oauth with a code
Now you have to request the access token doing a POST to the providers endpoint. Example:
POST PROVIDER?code=d5Q3HC7EGNH36SE3N&
client_id=d4HQNPFIXFD255H&
client_secret=1a98b7cb92407cbd8961cd8db778de53&
redirect_uri=https://example.com/oauthDemo&
grant_type=authorization_code
Now you have the access_token and you can use it to get information or decode it using JWT.
Having this clear, and seeing that your call seems corect:
#<struct Faraday::Request method=:post, path="PROVIDER/oauth/access_token", params={}, headers={"User-Agent"=>"Faraday v0.9.2", "Content-Type"=>"application/x-www-form-urlencoded"}, body={"grant_type"=>"authorization_code", "code"=>"aPexxxvUg", "client_id"=>"xxxxxur303GXEch7QK9k", "client_secret"=>"xxxxxxcad97b3d252e2bcdd393a", :redirect_uri=>"MYWEB/auth/my_app_strategy/callback"}, options=#<Faraday::RequestOptions (empty)>>
As the response is "HTTP Status 400 - Inadequate OAuth consumer credentials.", I think maybe you:
a. Your client is not well configured on the Provider. Usually you use to have a basic configuration on the provider site so he can recognise you. So maybe is not well configured.
b. There is a resource/permission/scope parameter missing or wrong configured on the first step (in the redirection to the provider). So when you ask for the token there is a problem.
I'm trying to authenticate a user with Instagram gem. So I have a page with code param returned by instagram, and I only need to send it back with POST request. According to gem documentation I need to do something like:
get "/oauth/callback" do
response = Instagram.get_access_token(params[:code], :redirect_uri => CALLBACK_URL)
session[:access_token] = response.access_token
redirect "/feed"
end
so I have
def authenticate
response = Instagram.get_access_token(params[:code], :redirect_uri => "http://127.0.0.1:3000")
session[:access_token] = response.access_token
redirect "/feed"
end
And I'm getting
Completed 500 Internal Server Error in 957ms
Instagram::BadRequest (POST https://api.instagram.com/oauth/access_token/: 400):
app/controllers/instagram_controller.rb:25:in `authenticate'
I tried making curl request as per Instagram api documentation, and it works with the same params.
About client_id, I store those keys in instagram.rb in initializers, so it looks like
require "instagram"
Instagram.configure do |config|
config.client_id = "123345"
config.client_secret = "123123"
end
CALLBACK_URL = "http://127.0.0.1:3000"
Thanks a lot in advance.
Found the problem. So, I have devise in my app as well, and I didn't put "redirect_url" in config.omniauth over there. Exactly the same thing people talking about over here https://github.com/Instagram/instagram-ruby-gem/issues/22.
Thanks for help.
I have Omniauth set up with a WePay strategy ( https://github.com/intridea/omniauth ). When getting authorization, it is making the v2/oauth2/token call four times (interspersed with /v2/user calls), but returns on the first access token in the env["omniauth.auth"] variable. This results in both long load times on the callback and a "access_token revoked" error when trying to perform an API call later.
I'm completely flummoxed as to why this is happening. I've tried disabling every method that follows the callback, so I'm pretty sure this is happening within Omniauth itself, as opposed to my app (which is in Rails, btw).
Here's my omniauth.rb initializer file:
require "omniauth/strategies/wepay"
OmniAuth.config.logger = Rails.logger
Rails.application.config.middleware.use OmniAuth::Builder do
provider :wepay, ENV['WEPAY_STAGE_APP_ID'], ENV['WEPAY_STAGE_SECRET']
provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
provider :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET']
end
Relevant routes:
match 'auth/wepay/callback', to: 'sessions#wepay'
match 'auth/failure', to: redirect('/organization')
The sessions controller (though I am reasonably confident the loop is happening before this ever gets called):
class SessionsController < ApplicationController
before_filter :get_all_organizations
before_filter :authorize_current_organization
def wepay
current_user.from_omniauth(env["omniauth.auth"])
if #organization.wepay_account_id? == false
#organization.create_wepay_account(current_user)
end
redirect_to transactions_path, notice: 'Login successful.'
end
end
The relevant section of my log:
Started GET "/auth/wepay/" for 127.0.0.1 at 2012-08-26 17:40:18 -0700
(wepay) Request phase initiated.
Started GET "/auth/wepay/callback?code=XXXXX&state=XXXXX" for 127.0.0.1 at 2012-08-26 17:40:25 -0700
(wepay) Callback phase initiated.
Connected to NewRelic Service at collector-6.newrelic.com
Processing by SessionsController#wepay as HTML
Parameters: {"code"=>"XXXXX", "state"=>"XXXXX"}
I'm having a tough time debugging this, but New Relic does show that 548ms are being spent in sessions#wepay, vs 261ms in Net::HTTP[stage.wepayapi.com]: POST. I don't know if that's indicative of anything.
Cool, just checking. On my end, as a test, I just ran the gem through a couple of Rails Omniauth premade apps, both of which were easily able to authenticate and return back to the app. I used these to create the apps:
http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
and
http://railsapps.github.com/tutorial-rails-mongoid-omniauth.html
I would check the session controller code, as it may not just be a routing issue.
I'm currently following along with this railscast and for my specific situation am running into a Faraday timeout error on the callback from omniauth.
Currently I'm using a rails application as an API and backbone as a javascript front-end (on the same application)
I decided I wanted to lock down the API with OAuth and provided a custom strategy for Omniauth to access the API as a client as well as Doorkeeper to handle the authorization logic
module OmniAuth
module Strategies
class Twiddle < OmniAuth::Strategies::OAuth2
option :name, :twiddle
option :client_options, {
site: "http://localhost:3001",
authorize_path: "/oauth/authorize"
}
uid do
raw_info["id"]
end
info do
{
firstName: raw_info["firstName"],
lastName: raw_info["lastName"],
email: raw_info["email"]
}
end
def raw_info
#raw_info ||= access_token.get('/api/v1/user').parsed
end
end
end
end
I included the custom strategy like this:
require File.expand_path('lib/omniauth/strategies/twiddle', Rails.root)
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twiddle, id, secret # Omitting the actual ones for obvious reasons
end
I am currently using these gems in my bundle
# OAuth
gem 'oauth2'
gem 'omniauth'
gem 'omniauth-oauth2'
gem 'omniauth-facebook'
gem 'doorkeeper'
Here is where I authenticate and attempt to retrive the proper access token (and also where I get stuck)
def loginParse
if ( user = User.authenticate( params[:email], params[:password] ) )
session[:user_id] = user.id
redirect_to '/auth/twiddle/'
else
render :controller => "authentication", :action => "loginIndex", :notice => "Incorrect credentials"
end
end
Here is the routing from the routes.rb
# Oauth urls
match '/auth/twiddle/callback', to: "authentication#connectAPI"
match "/auth/facebook/callback", to: "authentication#loginSocialMedia"
The application never is able to render the connectAPI action, getting COMPLETELY stuck at this point (given by the server logs)
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
Doorkeeper::Application Load (0.2ms) SELECT `oauth_applications`.* FROM `oauth_applications` WHERE `oauth_applications`.`uid` = '' LIMIT 1
CACHE (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
Doorkeeper::AccessToken Load (0.3ms) SELECT `oauth_access_tokens`.* FROM `oauth_access_tokens` WHERE `oauth_access_tokens`.`revoked_at` IS NULL AND `oauth_access_tokens`.`application_id` = 1 AND `oauth_access_tokens`.`resource_owner_id` = 1 ORDER BY created_at desc LIMIT 1
(0.1ms) BEGIN
Doorkeeper::AccessGrant Load (0.2ms) SELECT `oauth_access_grants`.* FROM `oauth_access_grants` WHERE `oauth_access_grants`.`token` = '' LIMIT 1
SQL (1.1ms) INSERT INTO `oauth_access_grants` (`application_id`, `created_at`, `expires_in`, `redirect_uri`, `resource_owner_id`, `revoked_at`, `scopes`, `token`) VALUES (1, '2012-08-08 03:10:31', 600, 'http://localhost:3001/auth/twiddle/callback', 1, NULL, '', '')
(1.4ms) COMMIT
Redirected to http://localhost:3001/auth/twiddle/callback?code=a
Completed 302 Found in 12ms (ActiveRecord: 3.7ms)
(twiddle) Callback phase initiated.
Many of the uids/important information have been omitted from the log.
Finally this error is given:
Faraday::Error::TimeoutError (Timeout::Error):
I hope I have been thorough in my explanation of this problem.
I don't know why exactly the application seems to be freezing at the callback initiated part of omniauth. I have tried updating bundler as a few other stackoverflow questions have pointed me to but it is not working.
Perhaps my understanding of OAuth2 is a bit murky.
If anyone can help me, I would greatly appreciate it
I'm not sure this applies to you been here was my scenario:
Problem
An app with data that our internal OAuth server wants
An OAuth server with little, to no data on it
We want to offload the authentication portion of App1 to App2 without moving data
Solution
App1 uses App2 as the Authentication server
App2 uses App1 for user data
Problem from this solution
Deadlock - App1 is waiting for an OAuth response from App2, but to complete
that response App2 must wait for a response from App1.
Final observation
In development mode on Rails (with WebBrick) you can't run multi-threaded, so the request
may never be allowed to complete.
My solution
My solution was to install puma and add to
config/environments/development.rb:
if ENV["THREADS"]
config.threadsafe!
end
Then when you start the server you'd do THREADS=1 rails s Puma to test your
OAuth stuff.
OR
Or your scenario is completely different and you're actually not communicating between
your services. Is your extra_info (like Github's /user) endpoint functioning on
the OAuth server? Is your callback actually doing anything?
I hope this helps!
I am building a 2-Legged OAuth provider for my api. Everything is hooked up properly and I can make signed calls from the rails console. The problem I have is that I am having trouble integrating OAuth into the controller_spec.
Here is an example of a working call on my server:
coneybeare $ rails c test
Loading test environment (Rails 3.2.0)
rails test: main
>> consumer = OAuth::Consumer.new("one_key", "MyString", :site => [REDACTED])
# => #<OAuth::Consumer:0x007f9d01252268 #key="one_key", #secret="MyString", #options={:signature_method=>"HMAC-SHA1", :request_token_path=>"/oauth/request_token", :authorize_path=>"/oauth/authorize", :access_token_path=>"/oauth/access_token", :proxy=>nil, :scheme=>:header, :http_method=>:post, :oauth_version=>"1.0", :site=>[REDACTED]}>
ruby: main
>> req = consumer.create_signed_request(:get, "/api/v1/client_applications.json", nil)
# => #<Net::HTTP::Get GET>
ruby: main
>> res = Net::HTTP.start([REDACTED]) {|http| http.request(req) }
# => #<Net::HTTPOK 200 OK readbody=true>
ruby: main
>> puts res.body
{"client_applications":[{"id":119059960,"name":"FooBar1","url":"http://test1.com"},{"id":504489040,"name":"FooBar2","url":"http://test2.com"}]}
# => nil
And here is what I am doing in my controller tests:
require 'oauth/client/action_controller_request'
describe Api::ClientApplicationsController do
include OAuthControllerSpecHelper
…
…
it "assigns all client_applications as #client_applications" do
consumer = OAuth::Consumer.new("one_key", "MyString", :site => [REDACTED])
ActionController::TestRequest.use_oauth=true
#request.configure_oauth(consumer)
#request.apply_oauth!
puts "request.env['Authorization'] = #{#request.env['Authorization']}"
get :index, {:api_version => 'v1', :format => :json}
response.should be_success # Just this for now until I can get authorization, then proper controller testing
end
end
The output of that test:
request.env['Authorization'] = OAuth oauth_consumer_key="one_key", oauth_nonce="gzAbvBSWyFtIYKfuokMAdu6VnH39EHeXvebbH2qUtE", oauth_signature="juBkJo5K0WLu9mYqHVC3Ar%2FATUs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1328474800", oauth_version="1.0"
1) Api::ClientApplicationsController GET index assigns all client_applications as #client_applications
Failure/Error: response.should be_success
expected success? to return true, got false
And the corresponding server call from the rails log:
Processing by Api::ClientApplicationsController#index as JSON
Parameters: {"api_version"=>1}
Rendered text template (0.0ms)
Filter chain halted as #<OAuth::Controllers::ApplicationControllerMethods::Filter:0x007f85a51a8858 #options={:interactive=>false, :strategies=>:two_legged}, #strategies=[:two_legged]> rendered or redirected
Completed 401 Unauthorized in 15ms (Views: 14.1ms | ActiveRecord: 0.0ms)
(0.2ms) ROLLBACK
I just can't figure out why it's not working :/ Am I making an obvious mistake?
If you'd like to test it in a request spec and actually need to test without stubbing, you can build an OAuth consumer and sign a request like this:
#access_token = FactoryGirl.create :access_token
#consumer = OAuth::Consumer.new(#access_token.app.key, #access_token.app.secret, :site => "http://www.example.com/")
#path = "/path/to/request"
#request = #consumer.create_signed_request(:get, #path, OAuth::AccessToken.new(#consumer, #access_token.token, #access_token.secret))
get #path, nil, { 'HTTP_AUTHORIZATION' => #request.get_fields('authorization').first }
I would take a look as to how the Omniauth test helpers work, specifically these files: https://github.com/intridea/omniauth/tree/master/lib/omniauth/test. See their wiki page on integration testing for ideas of how this is set up. I realize that you're building a provider, not a client, but this may be a good starting point. Also, as some of the commenters have already said, I don't know if you can do this with a controller test; you may need a request or integration test to fully simulate the rack environment.
Turns out that the best way to test my controller was the simplest as well. Instead of trying to sign each test so the controller gets the right information (something that indeed does belong in a request spec not a controller spec), I figured out that I could just give the controller the information it needed manually.
To do this, I simply had to stub 2 methods:
fixtures :client_applications
before(:each) do
#client_application1 = client_applications(:client_application1)
Api::ClientApplicationsController::Authenticator.any_instance.stub(:allow?).and_return(true)
controller.stub(:client_application).and_return(#client_application1)
end
Stubbing the allow? method caused the rack auth to be fooled into thinking it was authenticated. allow? also set the client_application based on the credentials though, so I had to stub that as well. Now that the auth is out of the way, I can test my controller properly.