9

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

I-am-simple-user
  • 409
  • 7
  • 19
  • 3
    You've exposed your consumer key and consumer secret here. I've attempted to edit them out, however it will need to be confirmed by others before the edit goes live. Please be sure to have these credentials cancelled right away and get new credentials, which you keep private. – Scott Swezey May 23 '16 at 18:31
  • @ScottS. Thanks for your reply I will take care of it. BTW those are the random numbers. – I-am-simple-user May 24 '16 at 09:28
  • Is there a reason why you did not use https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb to setup your oauth ? – Ravi Sankar Raju May 25 '16 at 22:41
  • 1
    @RaviSankarRaju, yes actually first I tired to use it but getting error that 'oauth_token' not present. because of this I manually created consumer for the request phase and used it. – I-am-simple-user May 26 '16 at 04:59

1 Answers1

4

First of all, I wan to make clear how Oauth2 works:

Oauth2, the protocol says:

  1. 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

  2. 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

  3. 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.

María Valero
  • 216
  • 2
  • 9
  • 1
    Had discussion with the provider team they said they are supporting the `OAuth1` not the `OAuth2` so made the changes in the strategy file. And it works find able to fetch the details (access_token). Thanks for the reply. – I-am-simple-user Jun 06 '16 at 04:56