将Omniauth Facebook登录转换为弹出窗口

58
我正在使用Rails的Omniauth gem,它可以很好地登录用户,但每次都会带你到Facebook登录页面,然后重定向回来。我想知道是否有一种方法可以像大多数页面那样在弹出窗口中显示Facebook登录,完成后重新加载父DIV。有什么建议吗?谢谢!

9
混淆的根源在于误解 omniauth 中 :display => "popup" 选项的目的 - 这个选项是告诉 Facebook 我们想要页面的“显示样式”适合弹出窗口的大小/格式。这个选项并不表示“将它作为浏览器窗口弹出” - 弹出操作由你来执行,就像 Chris Heald 展示的那样。尝试使用 Chris 的答案,并在带有和不带有 :display => "popup" 选项的情况下查看登录页面,在每次更改选项之间重新启动服务器,你会发现不同之处。 - Zabba
4个回答

125

当然可以,非常简单。

在你的视图中:

=link_to "Log in with Facebook", omniauth_authorize_path(:user, :facebook), :class => "popup", :"data-width" => 600, :"data-height" => 400

在你的 application.js 文件中:

function popupCenter(url, width, height, name) {
  var left = (screen.width/2)-(width/2);
  var top = (screen.height/2)-(height/2);
  return window.open(url, name, "menubar=no,toolbar=no,status=no,width="+width+",height="+height+",toolbar=no,left="+left+",top="+top);
}

$("a.popup").click(function(e) {
  popupCenter($(this).attr("href"), $(this).attr("data-width"), $(this).attr("data-height"), "authPopup");
  e.stopPropagation(); return false;
});

然后在你的回调视图中:

:javascript
  if(window.opener) {
    window.opener.location.reload(true);
    window.close()
  }

这将在一个居中的600x400弹出窗口中显示您的Facebook授权界面,然后当用户从认证返回时,视图将关闭弹出窗口并刷新父页面。如果用户Ctrl +单击链接或未启用Javascript,则会优雅地降级。


1
我正在使用Railscast Omniauth方法,回调可能基于各种逻辑条件使用4个不同的重定向。所以我把你的回调JavaScript放在application.js中,假设我不会再使用弹出窗口,它就有效了。唯一我不喜欢的是,当您输入FB密码登录后,下次登录时,弹出窗口仍然会短暂显示。我希望只有在需要用户输入时才显示弹出窗口。有没有办法实现这一点? - Dex
3
另一个需要注意的是,在IE7、8中 window.opener.location.whatever 不起作用。您需要将该代码放在父窗口中,并将其作为函数调用,例如 window.opener.my_goto_function(myurl) - Dex
4
它还应该显示为一个弹出窗口,而不是一个页面。要做到这一点,请按照https://dev59.com/QG855IYBdhLWcg3wMRRV中的说明进行操作。 - e3matheus
要在弹出窗口而不是页面中显示,请使用:/auth/facebook?display=popup。换句话说,omniauth_authorize_path(:user, :facebook,:display=>"popup")。 - montrealmike
2
我同意Ashitaka的观点。由于弹出窗口被重定向到显示闪存消息的页面(用户几乎看不到),然后关闭弹出窗口并重新加载主页面,因此我失去了闪存消息。闪存消息消失了,所以用户不知道事情进展顺利。我们如何关闭弹出窗口并重新加载页面,而不是进行必要的重定向? - Kevin Elliott
显示剩余6条评论

29

如果你正在使用OmniAuth和Devise,那么Chris Heald的解决方案存在问题。问题在于,当你重新加载窗口(即在登录页面上),Devise会将你带到root_path,完全忽略你尝试访问的URL,并显示错误消息“您已经登录”。这是有道理的,因为Devise通过重定向到主页来保护已登录用户免于访问登录页面。在登录后立即重新加载登录页面,就会出现这个问题。

因此,对于使用Devise的人,我的解决方案如下:

# add this wherever needed in your_authentications_or_callbacks_controller.rb
sign_in user
@after_sign_in_url = after_sign_in_path_for(user)
render 'callback', :layout => false

通常,使用某个提供程序(Facebook、Twitter等)返回的哈希值找到或创建用户后,我们会调用Devise函数sign_in_and_redirect。但此时我们不能立即重定向(请记住,现在用户当前在弹出窗口中),因此我们只需简单地登录用户 sign_in

接下来,我们需要将用户试图访问的URL传递给视图,我们可以使用Devise的方法after_sign_in_path_for来获取该URL。

最后,我们需要呈现这个视图。由于我们只会使用视图来调用一些JavaScript,所以没有必要渲染布局,我们关闭它以不拖慢速度。下面是那个视图:

# views/your_authentications_or_callbacks/callback.html.erb
<script type="text/javascript">
  window.opener.location = '<%= @after_sign_in_url %>';
  window.close();
</script>

使用这种方式,用户在登录后将被重定向到正确的URL,并显示正确的提示信息。

禁用JavaScript时

经过一些测试,我意识到这种解决方案无法在没有JavaScript的情况下进行身份验证,因此我想添加一个附注。

function popupCenter(linkUrl, width, height, name) {
    var separator = (linkUrl.indexOf('?') !== -1) ? '&' : '?',
        url = linkUrl + separator + 'popup=true',
        left = (screen.width - width) / 2,
        top = (screen.height - height) / 2,
        windowFeatures = 'menubar=no,toolbar=no,status=no,width=' + width +
            ',height=' + height + ',left=' + left + ',top=' + top;
    return window.open(url, name, windowFeatures);
}

这里的修改是通过JavaScript向URL添加一个简单的名为popup的参数。 OmniAuth会很好地存储添加到请求URL中的任何查询参数。 最后我们在控制器中检查该参数是否存在。 如果存在,那么说明JavaScript已启用:

if request.env['omniauth.params']['popup']
  render 'callback', :layout => false
else
  redirect_to @after_sign_in_url
end

同时,别忘了对于failure操作也做同样的处理,这个操作会在用户不接受登录时被调用。

如果没有Chris Heald的解决方案,我可能无法完成这个任务,所以...非常感谢!


这是一种处理弹出式登录的简单有效的方法,但我想知道在这种情况下如何使用“改造后的Facebook登录表单”。因为登录表单将以非常丑陋的方式显示。 - Codii
你说的丑陋是什么意思?这是一个Facebook弹出窗口。你可以稍微自定义一下(添加图片,更改标题),但只能这样做。你不应该更改它,否则人们就无法将弹出窗口识别为属于Facebook。正如Chris Heald在另一条评论中所说,“委托身份验证的一个重要部分是认证站点授予的信任不是身份验证过程的一部分。”通过更改表单,你会破坏这种信任。 - Ashitaka
仔细看一下Chris Heald的回答。看看他的link_to。他在链接中添加了一些数据参数。如果你使用Rails的新哈希语法,它会看起来更好。像这样:data: {width: '640', height: '330'} - Ashitaka
你真的认为现在禁用 JS 仍然可以正常工作吗?我知道如果可以,为什么不呢,但如果有人想要实现弹出窗口版本,那么很可能是为了之后应用更多的 JS 逻辑,不是吗? - Augustin Riedinger
不一定。例如,在这种情况下,我只使用JS打开一个包含不同页面的弹出窗口。我也可以轻松地重定向到该页面。但是,我使用JS是因为它使体验更加无缝。但是,有些用户可能没有启用JS或者连接速度较慢导致JS尚未加载...因此,我认为渐进增强仍然很重要。 - Ashitaka
显示剩余2条评论

4

分享一下以帮助他人。我正在使用Chris Heald的答案,但在最后一个javascript片段中遇到了问题,因为它会关闭任何新窗口链接。例如,如果我将我的网站链接发布到Facebook上,当用户单击链接时,由于条件仅检查“if(window.opener)”,在Chrome中新窗口将自动关闭。

最终我使用了全局变量(popupValue)来解决这个问题。可能有更优雅的解决方案,但是我想与其他人分享以防他们遇到相同的问题:

function popupCenter(url, width, height, name) {
 var left = (screen.width/2)-(width/2);
 var top = (screen.height/2)-(height/2);
 popupValue = "on";
 return window.open(url, name, "menubar=no,toolbar=no,status=no,width="+width+",height="+height+",toolbar=no,left="+left+",top="+top     );
}

$(document).ready(function(){
$("a.popup").click(function(e) {
popupCenter($(this).attr("href"), $(this).attr("data-width"), $(this).attr("data-height"), "authPopup");
e.stopPropagation(); return false;
});


if(window.opener && window.opener.popupValue === 'on') {
 delete window.opener.popupValue;
 window.opener.location.reload(true);
 window.close()
}
});

在IE9中,删除“window.opener.popupValue;”这一行不起作用,会出现“对象不支持此操作”的错误。但在Chrome和FF中可以正常工作。 - Alexander Savin

3

我最终使用了Facebook的JS SDK,因为它更容易操作。

# In facebook.js.coffee
jQuery ->
  $('body').prepend('<div id="fb-root"></div>')

  $.ajax
    url: "#{window.location.protocol}//connect.facebook.net/en_US/all.js"
    dataType: 'script'
    cache: true

window.fbAsyncInit = ->
  FB.init(appId: 'YOUR-APP-ID', cookie: true)

  $('#sign_in').click (e) ->
    e.preventDefault()
    FB.login (response) ->
      window.location = '/auth/facebook/callback' if response.authResponse

  $('#sign_out').click (e) ->
    FB.getLoginStatus (response) ->
      FB.logout() if response.authResponse
    true

那么在您的视图中:

<%= link_to "Sign in with Facebook", "/auth/facebook", id: "sign_in" %>
<%= link_to "Sign out", signout_path, id: "sign_out" %>

这是直接来自Sergio Gutierrez的提示 - https://coderwall.com/p/bsfitw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接