Turbolinks Deivse 登录相关

我们想要在使用devise登录的时候实现用在form提交时候使用remote: true来提交表单。表单提交通过之后使用Turbolinks跳转到其他页面。
如果有验证错误则在当前页面显示验证错误。

为什么要这样做? 因为在Turbolinks-ios中无法用html方法提交表单。提交表单之后的跳转也必须由turbolinks来完成。

首先准备好需要render的js模版。

1
2
3
4
5
6
7
8
9
10
11
12
13
<% if resource && resource.errors.empty? %>
<% flash[:success] = "登录成功"; %>
Turbolinks.visit('<%=@redirect_to %>',{ action: "replace" });
<% else %>
$('#headFlash').html(`
<div class="alert alert-warning alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<span><%= flash[:alert] %></span>
</div>
`);
<% end %>

我们需要overwrite devise的sessionsController中的create方法。

1
2
3
4
5
6
7
8
9
class Users::SessionsController < Devise::SessionsController

def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
@redirect_to = after_sign_in_path_for(resource)
end
end

注意需要在router配置中标明我们进行了Devise::SessionsController的overwrite。

跑一下。登录成功,跳转。

故意输错密码,登录失败,但是直接server端返回了401,而并没有渲染create.js.erb模版。

这个是为什么?

重点在这里。warden.authenticate!(auth_options) warden的默认策略直接跳过下面的步骤直接返回给我们401了。我们需要告诉warden我们期待的是什么处理。

这里需要对oauth_options中的recall 进行overwrite。

注意到使用remote: true默认的request的format是:js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Users::SessionsController < Devise::SessionsController

respond_to :js
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
@redirect_to = after_sign_in_path_for(resource)
end

def failure
render :create
end
protected
def auth_options
{ scope: resource_name, recall: "#{controller_path}#failure" }
end
end

故意输错一下试一试,特么为什么还是直接返回401而没有使用我们定义的failure方法?
去看一下devise initializer里面配置的说明。

1
2
3
4
5
6
7
8
9
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
# The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html]

那么就是说只要config.navigational_formats = ['*/*', :html, :js]就ok了。

添加之后再试一下,还是不行。搜索一番。据说要添加

1
config.http_authenticatable_on_xhr = false

这特么是什么鬼。看一下devise default的failure app。

1
2
3
4
5
6
7
8
9
def respond
if http_auth?
http_auth
elsif warden_options[:recall]
recall
else
redirect
end
end

我们看到只有http_auth? 为false我们才会调用recall。

1
2
3
4
5
6
7
def http_auth?
if request.xhr?
Devise.http_authenticatable_on_xhr
else
!(request_format && is_navigational_format?)
end
end

可以看到我们只有设置Devise.http_authenticatable_on_xhr 为 false才使得验证失败时候的处理落到我们自己定义的recall中。

以上。

如果你觉得本文对你有帮助,请给我点赞助。