title: Omniauth custom `callback_path` makes `env['omniauth.auth']` return nil. Tried few approaches fruitful to some extent but failing in the end
body: <p><strong>Background</strong></p>
  
  <p>My application requires me to connect to two different Facebook apps for different purposes and for which I am using below shown code.</p>
  
  <p><strong>Problem</strong></p>
  
  <p>When using a custom <strong>callback_path</strong> like in method <code>facebook_opts_for_social_sharing</code> shown below, in the
  callback handler i.e. in my <code>ExternalApiAuthController#create_social_sharing_auth_account</code> action <code>request.env['omniauth.auth']</code> is returned as nil.
  A similar issue is found reported in <a href="https://github.com/intridea/omniauth/issues/767" rel="nofollow">intridea/omniauth</a> repo.</p>
  
  <p><strong>/config/routes.rb</strong></p>
  
  <pre><code>  get '/auth/:provider/callback', to: 'external_api_auth#create'
    get '/auth/:provider/social_sharing/callback', to: 'external_api_auth#create_social_media_platform_account', as: :social_sharing_auth
    get '/auth/failure', to: 'external_api_auth#failure'
  </code></pre>
  
  <p><strong>/app/controllers/external_api_auth_controller.rb</strong></p>
  
  <pre><code>  class ExternalApiAuthController
      # GET /auth/failure
      def failure
      end
  
      # GET /auth/:provider/callback
      def create
      end
  
      # GET /auth/:provider/social_sharing/callback
      def create_social_media_platform_account
      end
    end
  </code></pre>
  
  <p><strong>/config/initializers/omniauth.rb</strong></p>
  
  <pre><code>    def provider_facebook
        'facebook'
      end
  
      def facebook_opts
        my_model_obj = MyModelService.find_by_provider_name(provider_facebook)
  
        return unless my_model_obj.present?
  
        app_details_hash = my_model_obj.application_details
        client_id = app_details_hash[:client_id]
        client_secret = app_details_hash[:client_secret]
  
        return if client_id.blank? || client_secret.blank?
  
        {
          client_id: client_id,
          client_secret: client_secret,
          scope: 'email,manage_pages,publish_pages',
          display: 'popup'
        }
      end
  
      def facebook_opts_for_social_sharing
        my_model_obj = MyAnotherModelService.find_by_internal_name(provider_facebook)
  
        return unless my_model_obj.present?
  
        app_details_hash = my_model_obj.application_details
        client_id = app_details_hash[:client_id]
        client_secret = app_details_hash[:client_secret]
  
        return if client_id.blank? || client_secret.blank?
  
        {
          client_id: client_id,
          client_secret: client_secret,
          scope: 'email,manage_pages,publish_pages',
          display: 'popup',
          callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook)
        }
      end
  
      SETUP_PROC = lambda do |env|
        strategy_instance = env['omniauth.strategy']
        provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)
  
        request = Rack::Request.new(env)
  
        is_social_sharing_auth = false
  
        auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
        if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
          is_social_sharing_auth = true
        end
  
        opts = case provider_name.downcase.underscore
                when 'facebook'
                  ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
                else
                  nil
               end
  
        if opts.present?
          env['omniauth.strategy'].options.merge!(opts)
        end
      end
  
      OmniAuth.config.logger = Rails.logger
  
      OmniAuth.config.on_failure do |env|
        .....
        .....
      end
  
      Rails.application.config.middleware.use OmniAuth::Builder do
        # Reference: https://github.com/intridea/omniauth/wiki/Setup-Phase
        provider :facebook, setup: SETUP_PROC
      #end
  </code></pre>
  
  <p>With that code what is happening is the <code>callback_path</code> is correctly being picked-up during <strong>Request Phase</strong>.
  However as soon as the request phase finished and <a href="https://github.com/intridea/omniauth-oauth2/blob/v1.4.0/lib/omniauth/strategies/oauth2.rb#L48" rel="nofollow">OmniAuth::Strategies::OAuth2#request_phase</a>
  initiated redirect, only the <a href="https://github.com/mkdynamic/omniauth-facebook/blob/master/lib/omniauth/strategies/facebook.rb" rel="nofollow">OmniAuth::Strategies::Facebook.default options</a>
  are being used by <a href="https://github.com/intridea/omniauth/blob/v1.3.1/lib/omniauth/strategy.rb#L172" rel="nofollow">OmniAuth::Strategy</a> instance.
  As these options don't contain the <code>callback_path</code> (<strong>after redirect was initiated</strong>) <code>on_callback_path?</code> while evaluating following
  line <a href="https://github.com/intridea/omniauth/blob/v1.3.1/lib/omniauth/strategy.rb#L184" rel="nofollow"><code>return callback_call if on_callback_path?</code></a>
  always returns false and hence callback phase never get a chance to execute.</p>
  
  <p><strong>Approach 1</strong></p>
  
  <p>To get around this limitation I tried an approach of sending the <code>callback_path</code> in <a href="https://github.com/mkdynamic/omniauth-facebook/blob/master/lib/omniauth/strategies/facebook.rb" rel="nofollow">OmniAuth::Strategies::Facebook.default options</a>
  so that during each phase it gets picked up. Hence instead of passing it through the code in SETUP_PROC like in method <code>facebook_opts_for_social_sharing</code>,
  I passed it in following manner i.e. passed it as an option to <code>OmniAuth::Builder#provider</code> method invocation:</p>
  
  <p><code>
  Rails.application.config.middleware.use OmniAuth::Builder do
    provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook)
  end
  </code></p>
  
  <p>And to make it work updated SETUP_PROC to look like</p>
  
  <pre><code>    SETUP_PROC = lambda do |env|
        strategy_instance = env['omniauth.strategy']
        provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)
  
        request = Rack::Request.new(env)
  
        is_social_sharing_auth = false
  
        auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
        if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
          is_social_sharing_auth = true
        elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
          is_social_sharing_auth = true
        end
  
        opts = case provider_name.downcase.underscore
                when 'facebook'
                  ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
                else
                  nil
               end
  
        unless is_social_sharing_auth
          env['omniauth.strategy'].options.delete(:callback_path)
        end
  
        if opts.present?
          env['omniauth.strategy'].options.merge!(opts)
        end
      end
  </code></pre>
  
  <p>However this makes the <strong>custom callback_path</strong> scenario work but the <strong>default callback_path</strong> <code>/auth/facebook/callback</code> scenario fails
  because the <code>callback_path</code> option containing a custom callback_path is always available in <a href="https://github.com/intridea/omniauth/blob/v1.3.1/lib/omniauth/strategy.rb#L172" rel="nofollow">OmniAuth::Strategy</a> instance.</p>
  
  <p><strong>Approach 2</strong></p>
  
  <p>Thus to get around the limitation posed by <strong>Approach 1</strong> I tried another approach of using a middleware which based on the request's path_info and params invokes the strategy middleware with desired options.</p>
  
  <p><strong>/app/middleware/omniauth_builder_setup.rb</strong></p>
  
  <pre><code>    class OmniauthBuilderSetup
        def initialize(app)
         @app = app
        end
  
        def call(env)
          request = Rack::Request.new(env)
  
          Rails.logger.debug "&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #{@app.inspect}"
  
          provider_name = provider_name(request.path_info)
  
          unless provider_name
            status, headers, response = @app.call(env)
            return [status, headers, response]
          end
  
          is_social_sharing_auth = false
  
          auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
          if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
            is_social_sharing_auth = true
          elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
            is_social_sharing_auth = true
          end
  
          if is_social_sharing_auth
            middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
          else
            middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
          end
  
          Rails.logger.debug "&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"
  
          @app = middleware_instance
  
          status, headers, response = @app.call(env)
  
          [status, headers, response]
        end
  
        private
  
        def provider_name_regex
          # matches
          #  /auth/facebook
          #  /auth/facebook/callback
          #  /auth/facebook?auth_purpose=social_sharing
  
          /\A\/auth\/(facebook|twitter)(?:((\/.*)|(\?.+=.+))?)\z/
        end
  
        def provider_name(path_info)
          match_data = path_info.match(provider_name_regex)
  
          return if match_data.nil?
  
          match_data.captures.first
        end
  
        def omniauth_strategy_middleware(klass, *args, &amp;block)
          if klass.is_a?(Class)
            middleware = klass
          else
            begin
              middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
            rescue NameError
              raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
            end
          end
  
          args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
          middleware.new(middleware, *args, &amp;block)
        end
      end
  </code></pre>
  
  <p><strong>/config/application.rb</strong></p>
  
  <pre><code>  ....
  
      config.middleware.use "OmniauthBuilderSetup"
    ....
  </code></pre>
  
  <p><strong>/config/initializers/omniauth.rb</strong> (commented out <code>use OmniAuth::Builder</code>)</p>
  
  <pre><code>  ....
    ......
    .....
  
    #Rails.application.config.middleware.use OmniAuth::Builder do
    #  provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.reviews_social_sharing_auth_callback_path(provider: provider_facebook)
    #end
  </code></pre>
  
  <p>With this middleware approach the callback phase is initiated in both the scenarios i.e. when using default callback_path <code>/auth/facebook/callback</code> and a custom callback_path <code>/auth/facebook/social_sharing/callback</code>. But during callback phase but it fails with following error:</p>
  
  <pre><code>      undefined method `call' for OmniAuth::Strategies::Facebook:Class Did you mean? caller
  </code></pre>
  
  <p>I added few log statements in <a href="https://github.com/intridea/omniauth/blob/v1.3.1/lib/omniauth/strategy.rb#L172" rel="nofollow">OmniAuth::Strategy</a>
  and the following logs were generated.</p>
  
  <pre><code>      Started GET "/auth/facebook" for 127.0.0.1 at 2016-07-28 10:28:23 +0530
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;ActionDispatch::Routing::RouteSet:0x000000073a64c8&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup middleware_instance: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;OmniAuth::Strategies::Facebook&gt;
        (facebook) Setup endpoint detected, running now.
        (facebook) Request phase initiated.
  
  
        Started GET "/auth/facebook/callback?code=AQDxel76u_UvtTeSHUw3CzMpA98KTI4V_75qhxV5TGD7rdGcyeCX-FS1nrrlo-EAezZXUPdH9cAC5h4c1xlqoIL7UZ2WLDfXHG4GHWZTEGYHzH7QURNSkrjvDoBNWV90E83f_R6RURl1POsq8ZhmQOFD5YGXRxosiVx4Sof8_vqJZ5UT2S5SFbmVLEtaZZacJDqEbWjNKBrYdrZauuqCS91lEw6Lrz5U5rA2eOmmygAiBwso-cnmOuRu-PptwtIbBL5zw5hPOANQskIFHL-lfbobZYBwy_NsY8Nf-HsJauuymSmtfsQ28UaPlkox9vSinqDAHYhW1ltBXrOX_7P4HfBr&amp;state=3831c127892242fb43aaa2ebfe37cac9e0cd2c8dbea06f3e" for 127.0.0.1 at 2016-07-28 10:28:29 +0530
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup middleware_instance: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniAuth::Strategy call!(env) options #&lt;OmniAuth::Strategy::Options access_token_options=#&lt;OmniAuth::Strategy::Options header_format="OAuth %s" param_name="access_token"&gt; auth_token_params=#&lt;OmniAuth::Strategy::Options&gt; authorize_options=[:scope, :display, :auth_type] authorize_params=#&lt;OmniAuth::Strategy::Options&gt; client_id=nil client_options=#&lt;OmniAuth::Strategy::Options authorize_url="https://www.facebook.com/dialog/oauth" site="https://graph.facebook.com" token_url="oauth/access_token"&gt; client_secret=nil name="facebook" provider_ignores_state=false setup=#&lt;Proc:0x000000065ead70@/jwork/ruby/ror_projects/Reviewgo-JonathanSmith/reviewgo/config/initializers/omniauth.rb:76 (lambda)&gt; skip_info=false token_options=[] token_params=#&lt;OmniAuth::Strategy::Options parse=:query&gt;&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniAuth::Strategy call!(env) class: OmniAuth::Strategies::Facebook
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;OmniAuth::Strategy call!(env) current_path: /auth/facebook/callback
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;OmniAuth::Strategy call!(env) on_callback_path?: true
        (facebook) Setup endpoint detected, running now.
        (facebook) Callback phase initiated.
  
        NoMethodError (undefined method `call' for OmniAuth::Strategies::Facebook:Class
        Did you mean?  caller):
          app/middleware/omniauth_builder_setup.rb:61:in `call'
  </code></pre>
  
  <p>If you notice during the callback phase in my middleware the @app holds <strong>an instance of OmniAuth::Strategies::Facebook</strong> but as soon as the
  control reaches to OmniAuth::Strategy @app in OmniAuth::Strategy instance refers to class <code>OmniAuth::Strategies::Facebook</code>.</p>
  
  <pre><code>      &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup middleware_instance: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniauthBuilderSetup @app: #&lt;OmniAuth::Strategies::Facebook&gt;
        &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
  </code></pre>
  
  <p>I am sure there is some problem with my middleware. I haven't used middlewares before so don't understand this
  @app concept. Tried to refer to few resources on web to grasp it but no success.</p>
  
  <ol>
  <li><p>Can anybody please help me fixing up my middleware so that it can work in desired manner?</p></li>
  <li><p>If possible please try to make me understand the concept of @app and the status, headers and body values @app.call(env) should return.For e.g. in my case I need the middleware to proceed only if it matches desired omniauth paths. If not it should kind of skip and move ahead without interfering. I don't how to achieve this behavior.</p></li>
  </ol>
  
  <p>P.S. I am struggling to get around this limitation from past 2 days and with all the details given here, my findings, my approaches I am hoping that somebody from the community will definitely come forward to guide me in resolving my problem.</p>
  
  <p>Thanks.</p>