バックグラウンド
私のアプリケーションでは、さまざまな目的で 2 つの異なる Facebook アプリに接続する必要があり、そのために以下に示すコードを使用しています。
問題
以下に示すメソッドのようにカスタムcallback_pathfacebook_opts_for_social_sharing
を使用すると、コールバック ハンドラ、つまりExternalApiAuthController#create_social_sharing_auth_account
アクションrequest.env['omniauth.auth']
で nil が返されます。同様の問題がintridea/omniauthリポジトリで報告されています。
/config/routes.rb
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'
/app/controllers/external_api_auth_controller.rb
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
/config/initializers/omniauth.rb
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
そのコードで起こっていることは、Request Phasecallback_path
の間に正しくピックアップされていることです。ただし、リクエスト フェーズが終了し、OmniAuth::Strategies::OAuth2#request_phase
がリダイレクトを開始するとすぐに、OmniAuth::Strategies::Facebook.default オプションのみがOmniAuth::Strategyインスタンス
によって使用されます。これらのオプションには(リダイレクトが開始された後)が含まれていないため、次の行を評価する
と常に false が返されるため、コールバック フェーズが実行される機会がありません。callback_path
on_callback_path?
return callback_call if on_callback_path?
アプローチ1
この制限を回避するために、OmniAuth::Strategies::Facebook.default オプションを送信して、各フェーズで取得されるようにする方法を試しcallback_path
ました
。したがって、 method のように SETUP_PROC のコードを介して渡すのではなく、次の方法で渡しました。つまり、メソッド呼び出しfacebook_opts_for_social_sharing
のオプションとして渡しました。OmniAuth::Builder#provider
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook)
end
そして、それを機能させるために、SETUP_PROCを次のように更新しました
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
ただし、これによりカスタム callback_pathシナリオは機能しますが、カスタム callback_path を含むオプションはOmniAuth::Strategyインスタンスで常に使用できるため、デフォルトの callback_path /auth/facebook/callback
シナリオは失敗します。callback_path
アプローチ 2
したがって、アプローチ 1によってもたらされる制限を回避するために、リクエストの path_info と params に基づいて、必要なオプションで戦略ミドルウェアを呼び出すミドルウェアを使用する別のアプローチを試しました。
/app/middleware/omniauth_builder_setup.rb
class OmniauthBuilderSetup
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
Rails.logger.debug ">>>>>>>>>>>>> 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 ">>>>>>>>>>>>> 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, &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, &block)
end
end
/config/application.rb
....
config.middleware.use "OmniauthBuilderSetup"
....
/config/initializers/omniauth.rb (コメントアウトuse OmniAuth::Builder
)
....
......
.....
#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
このミドルウェア アプローチでは、デフォルトの callback_path/auth/facebook/callback
とカスタムの callback_pathを使用する場合など、両方のシナリオでコールバック フェーズが開始されます/auth/facebook/social_sharing/callback
。しかし、コールバック段階では、次のエラーで失敗します:
undefined method `call' for OmniAuth::Strategies::Facebook:Class Did you mean? caller
OmniAuth::Strategyにいくつかのログ ステートメントを追加したところ 、次のログが生成されました。
Started GET "/auth/facebook" for 127.0.0.1 at 2016-07-28 10:28:23 +0530
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<ActionDispatch::Routing::RouteSet:0x000000073a64c8>
>>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
(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&state=3831c127892242fb43aaa2ebfe37cac9e0cd2c8dbea06f3e" for 127.0.0.1 at 2016-07-28 10:28:29 +0530
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
>>>>>>>>>>>>> OmniAuth::Strategy call!(env) options #<OmniAuth::Strategy::Options access_token_options=#<OmniAuth::Strategy::Options header_format="OAuth %s" param_name="access_token"> auth_token_params=#<OmniAuth::Strategy::Options> authorize_options=[:scope, :display, :auth_type] authorize_params=#<OmniAuth::Strategy::Options> client_id=nil client_options=#<OmniAuth::Strategy::Options authorize_url="https://www.facebook.com/dialog/oauth" site="https://graph.facebook.com" token_url="oauth/access_token"> client_secret=nil name="facebook" provider_ignores_state=false setup=#<Proc:0x000000065ead70@/jwork/ruby/ror_projects/Reviewgo-JonathanSmith/reviewgo/config/initializers/omniauth.rb:76 (lambda)> skip_info=false token_options=[] token_params=#<OmniAuth::Strategy::Options parse=:query>>
>>>>>>>>>>>>> OmniAuth::Strategy call!(env) class: OmniAuth::Strategies::Facebook
>>>>>>>>>>>>>>OmniAuth::Strategy call!(env) current_path: /auth/facebook/callback
>>>>>>>>>>>>>>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'
ミドルウェアのコールバック フェーズ中に @app がOmniAuth::Strategies::Facebook のインスタンスを保持していることに気付いた場合、コントロールが OmniAuth::Strategy に到達するとすぐに、OmniAuth::Strategy インスタンス内の @app が class を参照しますOmniAuth::Strategies::Facebook
。
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
>>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
ミドルウェアに問題があると確信しています。以前にミドルウェアを使用したことがないため、この @app の概念を理解していません。それを把握するためにウェブ上のいくつかのリソースを参照しようとしましたが、成功しませんでした.
希望どおりに動作するようにミドルウェアを修正するのを手伝ってくれる人はいますか?
可能であれば、@app の概念と、@app.call(env) が返す必要があるステータス、ヘッダー、および本文の値を理解できるようにしてください。そうでない場合は、干渉せずにスキップして先に進む必要があります。この動作を達成する方法がわかりません。
PS 過去 2 日間からこの制限を回避するのに苦労しています。ここに記載されているすべての詳細、調査結果、アプローチを使用して、コミュニティの誰かが私の問題を解決するために私を導いてくれることを願っています。
ありがとう。