client_secret should not be passed to /oauth/token - ruby-on-rails

Is there any reference available to connect to Facebook using Oauth2 gem in Rails 4?
I am getting above error while calling get_token method of Oauth2.
def login_facebook
redirect_to client.auth_code.authorize_url(
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
)
end
def facebook_callback
token = client.auth_code.get_token(
params[:code],
:token_url => "/oauth/token",
#:token_url => "/oauth/access_token",
# :parse => :query,
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
)
puts "**************"
puts token.inspect
puts "**************"
end
private
def client
OAuth2::Client.new(APPLICATION_ID, APPLICATION_SECRET, :site => FB_GRAPH_URL)
end

The default url for requesting the access token has been changed from /oauth/access (which is what FB uses) to /oauth/access_token. Now, you need to override this setting as:
OAuth2::Client.new(FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, {
:token_url => '/oauth/access_token',
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
})
You may want to use Koala gem for easy communication with facebook.

Related

Rails: cannot find '*_path'

I'm trying to get a html button to redirect to my Azure login page from the homepage.
If I put in my browser localhost/3000/authentication/login it goes to the Azure web app.
This is what I'm trying to achieve with this:
<%= button_to 'Login', authentication_login_path, method: :get %>
My routes file is:
Rails.application.routes.draw do
root 'application#home'
get 'application/home'
get 'authentication/login', to: 'authentication#index'
resources :authentication
end
The Application and Home controllers just have blank functions at the moment.
I've tried rake | grep authentication and it contains the correct path:
authentication_login GET /authentication/login(.:format) authentication#index
Therefore, I'm not sure what doing authentication_login_path is not recognized in my home.html.erg file.
This is the Authentication_Controller. I'm trying to execute the index method as this will begin the authentication process.
require 'oauth2'
class AuthenticationController < ApplicationController
# You need to configure a tenant at Azure Active Directory(AAD) to register web app and web service app
# You will need two entries for these app at the AAD portal
# You will put clientid and clientsecret for your web app here
# ResourceId is the webservice that you registered
# RedirectUri is registered for your web app
CLIENT_ID = '56938f79-a23e-4f3f-a033-d23546d9056f'
CLIENT_SECRET = '5j8Hv8U1x_l-t047OZq9~LmK~kMdobV3rm'
AUTHORITY = 'https://login.windows.net/'
AUTHORIZE_URL = "https://beautytruth.b2clogin.com/beautytruth.onmicrosoft.com/B2C_1_btSignInSignOut/oauth2/v2.0/authorize"
TOKEN_URL = "https://beautytruth.b2clogin.com/beautytruth.onmicrosoft.com/B2C_1_btSignInSignOut/oauth2/v2.0/token"
RESOURCE_ID = '/subscriptions/eb589fa5-ed57-4e10-81c9-32e4284af10c/resourceGroups/btAdvertisingNetwork' #ResourceId or ResourceURI that you registered at Azure Active Directory
REDIRECT_URI = 'http://localhost:3000/welcome/callback'
def index
update_token
if session['access_token']
puts "Auth has been checked"
# show main page and use token
redirect_to
else
# start authorization
client = get_client
a = client.auth_code.authorize_url(:client_id => CLIENT_ID, :resource => RESOURCE_ID, :redirect_uri => REDIRECT_URI)
redirect_to(a)
end
end
def callback
begin
#code = params[:code]
client = get_client
# post token to mobile service api
#token = client.auth_code.get_token(CGI.escape(#code), :redirect_uri => REDIRECT_URI)
# id_token token.params["id_token"]
#multi resource token token.params["resource"]
token = client.auth_code.get_token(#code, :redirect_uri => REDIRECT_URI, )
session['access_token'] = token.token
session['refresh_token'] = token.refresh_token
session['expire_at'] = token.expire_at
session['instance_url'] = token.params['instance_url']
redirect '/'
rescue => exception
output = '<html><body><p>'
output += "Exception: #{exception.message}<br/>"+exception.backtrace.join('<br/>')
output += '</p></body></html>'
end
end
def update_token
puts "update token inside"
token = session['access_token']
refresh_token = session['refresh_token']
expire_at = session['expire_at']
#access_token = OAuth2::AccessToken.from_hash(get_client, { :access_token => token, :refresh_token => refresh_token, :expire_at => expire_at, :header_format => 'Bearer %s' } )
if #access_token.expired?
puts "refresh token"
#access_token = #access_token.refresh!
session['access_token'] = #access_token.token
session['refresh_token'] = #access_token.refresh_token
session['expire_at'] = #access_token.expire_at
session['instance_url'] = #access_token.params['instance_url']
end
end
# send post request to webservice to send token and create a post request
def use_token
# we got the token and now it will posted to the web service in the header
# you can specify additional headers as well
# token is included by default
update_token
conn = Faraday.new(:url => 'https://btadvertisingplatform.azurewebsites.net/') do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
response = conn.get do |req|
req.url '/api/WorkItem'
req.headers['Content-Type'] = 'application/json'
req.headers['Authorization'] = 'Bearer '+#access_token.token
end
#out = response.body
end
def get_client
client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, :site => AUTHORITY, :authorize_url => AUTHORIZE_URL, :token_url => TOKEN_URL )
client
end
end
As you already have resource :authentication you can use the following.
<%= button_to 'Login', authentication_index_path, method: :get %>
You don't need an additional route. You can remove the following route
get 'authentication/login', to: 'authentication#index'

Rails OAuth - Session variable is nil after redirect from QuickBooks Online API

I am trying to authenticate with QuickBooks Online via OAuth. After clicking authorize on QuickBooks' notification screen, it redirects to the callback url as expected but I do not have access to the request token object I created at the beginning.
I tried using a session variable but the variable is nil when called by QuickBooks. How can I do this?
class QuickBooksController < ApplicationController
def connect
#call_back_url = 'http://127.0.0.1:3000/quickbooks/callback'
#consumer = OAuth::Consumer.new('key', 'secret', {
:site => 'https://oauth.intuit.com',
:request_token_path => '/oauth/v1/get_request_token',
:authorize_url => 'https://appcenter.intuit.com/Connect/Begin',
:access_token_path => '/oauth/v1/get_access_token'
})
#request_token = #consumer.get_request_token(:oauth_callback => #callback_url)
session[:request_token] = #request_token
redirect_to #request_token.authorize_url(:oauth_callback => #call_back_url)
end
def callback
#This does not work because session[:request_token] is nil
#access_token = session[:request_token].get_access_token(:oauth_verifier => params[:oauth_verifier])
end
end

Getting 'undefined method `merge!' when merging SoundCloud exchange token

I'm working on an app that interacts with SoundCloud and I'm having an issue when I try to save the exchange_token that I'm getting back from the server (among other things) and I really could use some assistance.
According to the error I'm getting:
undefined method `merge!' for nil:NilClass
The problem apparently lies with line 10 in my sclouds_controller.rb file (included below):
soundcloud_client.exchange_token(:code => params[:code])
Which is calling a method in the SoundCloud gem that I'm using. Here's the line in the SoundCloud gem that the error originates from:
params.merge!(client_params)
That can be found on line 23 of the following method (taken from the client.rb file in the SoundCloud gem):
def exchange_token(options={})
store_options(options)
raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
client_params = {:client_id => client_id, :client_secret => client_secret}
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
params.merge!(client_params)
response = handle_response(false) {
self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
}
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
#options[:expires_at] = Time.now + response.expires_in if response.expires_in
#options[:on_exchange_token].call(*[(self if #options[:on_exchange_token].arity == 1)].compact)
response
end
However, if I throw a 'raise' in my sclouds_controller.rb file like this:
def connected
if params[:error].nil?
raise
soundcloud_client.exchange_token(:code => params[:code])
Then, in the console, manually paste in the following line:
soundcloud_client.exchange_token(:code => params[:code])
I get back the following response (which, to me, appears to be successful):
$ #<SoundCloud::HashResponseWrapper access_token="xxxxx" expires_in=21599 refresh_token="xxxxx" scope="*">
Any idea why this is happening? I'm trying to learn what I'm doing wrong, especially since I'm not sure if I'm going about this in the right way. Here's some of my code for a little more context. Thanks in advance!
sclouds_controller.rb:
class ScloudsController < ApplicationController
before_action :authenticate_user!, only: [:connect, :connected]
def connect
redirect_to soundcloud_client.authorize_url(:display => "popup")
end
def connected
if params[:error].nil?
soundcloud_client.exchange_token(:code => params[:code])
unless user_signed_in?
flash[:alert] = 's'
redirect_to :login
end
current_user.update_attributes!({
:soundcloud_access_token => soundcloud_client.access_token,
:soundcloud_refresh_token => soundcloud_client.refresh_token,
:soundcloud_expires_at => soundcloud_client.expires_at
})
end
redirect_to soundcloud_client.redirect_uri
end
def disconnect
login_as nil
redirect_to root_path
end
private
def soundcloud_client
return #soundcloud_client if #soundcloud_client
#soundcloud_client = User.soundcloud_client(:redirect_uri => 'http://localhost:3000/sclouds/connected/')
end
end
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :soundcloud_access_token, :soundcloud_refresh_token, :soundcloud_expires_at
has_one :scloud
#SOUNDCLOUD_CLIENT_ID = 'xxxxx'
#SOUNDCLOUD_CLIENT_SECRET = 'xxxxx'
#REDIRECT_URI = 'xxxxx'
def self.soundcloud_client(options={})
options = {
:client_id => #SOUNDCLOUD_CLIENT_ID,
:client_secret => #SOUNDCLOUD_CLIENT_SECRET,
:redirect_uri => #REDIRECT_URI
}.merge(options)
Soundcloud.new(options)
end
def soundcloud_client(options={})
client = self.class.soundcloud_client(options)
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
client.on_exchange_token do
self.update_attributes!({
:soundcloud_access_token => client.access_token,
:soundcloud_refresh_token => client.refresh_token,
:soundcloud_expires_at => client.expires_at
})
end
client
end
end
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
How does this work?
You're trying to set the options local var (which is a duplicate of the options argument in your method), and then you're trying to merge options? Looks like a self-referential loop to me...
Method
Secondly, you mention the error is caused by this line:
exchange_token(:code => params[:code])
I can't see this method in your documentation? The merge! method is obviously being caused inside this method somewhere - we need to know where it's being called, so we can fix it!
I can see one mention of merge, which I've queried above
Update
Thanks for posting the code!
I think #mischa is right - but I'll keep this code to show you what I'd look at..
I can only give an opinion, as I've never used this gem before -There are two calls to merge! in the gem code:
params.merge!(client_params)
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
I'd look at this:
Params
The params hash is populated locally like this:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
This could be the cause of the problem, as it is populated with elsif (not else). This means you have to make sure you're passing the right codes to the system
#options
#options is referenced but not declared
I imagine it's declared in another part of the gem code, meaning you need to make sure it's there. This will likely be set when the gem initializes (I.E when you set up the authentication between SC & your app)
If you've not got the #options set up correctly, it will probably mean you're not calling the gem correctly, hence the error.
Try mischa's fix, and see if that cures the issue for you.
Initializer
Something to note - convention for including gems in your system is to use an initializer
If you set up an initializer which creates a new constant called SOUNDCLOUD, you can then reference that throughout your app (curing any errors you have here)
I can write some code for you on this if you want
#RickPeck got very close I think, as the problem really lies in this code excerpt:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
#options is populated in the line store_options(options). So the problem is that neither options_for_refresh_flow_present?, options_for_credentials_flow_present?, nor options_for_code_flow_present? return true.
The relevant option for your code is code flow:
def options_for_code_flow_present?
!!(#options[:code] && #options[:redirect_uri])
end
which expects options to have both :code and :redirect_uri. In your code you pass only :code. Add a :redirect_uri and you should be good to go.
What #Mischa suggests will probably fix that for you, as your :redirect_uri was nil when you set it...

Faraday not working on Rails

I'm trying to use Faraday with Rails 4 for Google API Oauth in below code:
class GoogleApisController < ApplicationController
def oauth2callback
require 'faraday'
render layout: false
conn = Faraday.new(:url => 'https://accounts.google.com',:ssl => {:verify => false}) do |faraday|
faraday.request :url_encoded
faraday.response :logger
faraday.adapter Faraday.default_adapter
end
#result = conn.post '/o/oauth2/token', {'code' => params[:code],
'client_id' => "some_id",
'client_secret' => "some_secret",
'redirect_uri' => "some_redirect_uri",
'grant_type' => 'authorization_code'}
end
end
But when I do #result.body.inspect it doesn't return anything.
So #result return nil and doesn't seem to process at all.
However when I try the above codes in liveshell it seems to work.
Is there a reason why this is happening?
If you try to render before you instantiate the object you are trying to render, the template won't actually have anything in it. Moving the render statement to the bottom of the method will take care of the issue.

Rails Twitter Gem Omniauth

I'm having trouble using omniauth & twitter gem to generate tweets.
I have been using this tutorial http://blog.assimov.net/post/2358661274/twitter-integration-with-omniauth-and-devise-on-rails-3
and I can log in using twitter, generate authentications but I cannot update tweets.
current_user.twitter.update("My Rails 3 App with Omniauth, Devise and Twitter")
This line complains about the method update.
What confuses me about the tutorial above is that at the beginning they generate a migration to store the secret in authentications, but at no point later in the tutorial is any mention of changing any code to write the secret to the database.
My understanding is that the secret is obtained from the omniauth hash that is stored in the session cookie. What am I missing here?
def hash_from_omniauth(omniauth)
{
:provider => omniauth['provider'],
:uid => omniauth['uid'],
:token => (omniauth['credentials']['token'] rescue nil),
:secret => (omniauth['credentials']['secret'] rescue nil)
}
end
So everything seems to be working apart from creating the Twitter Client hence not having the update method available?
current_user.twitter.update("first tweet")
The twitter method here should be creating the Twitter Client
def twitter
debugger
unless #twitter_user
provider = self.authentications.find_by_provider('twitter')
#twitter_user = Twitter::Client.new(:oauth_token => provider.token, :oauth_token_secret => provider.secret )rescue nil
end
#twitter_user
end
I'm sorry I'm not great at explaining the problem. Any help greatly appreciated.
Thanks
L
If you only need to post to twitter, it might just be easier to forgo the twitter gem completely. The consumer / access token generation requires the app id and secret as well as the user's access and secret tokens. I found this to be easy enough to implement that I thought the overhead of the twitter gem wasn't necessary.
module User::Social
def self.included(base)
base.instance_eval do
include Rails.application.routes.url_helpers
end
end
def promote_activity(type, profile)
url = short_profile_url(profile, :host => Conf.domain)
tw_client.request(:post, "http://api.twitter.com/1/statuses/update.json", :status => I18n.translate("tweets.#{type}", :profile => profile.to_s, :url => url))
end
def tw_client
#tw_client ||= begin
consumer = OAuth::Consumer.new(Conf.tw_app_id, Conf.tw_secret, :site => 'http://api.twitter.com')
OAuth::AccessToken.from_hash(consumer, {:oauth_token => self.access_token, :oauth_token_secret => self.secret_token})
end
end
end
class User < AR::Base
include User::Social
end

Resources