这是Rails的JSON API认证(使用设计)安全吗? [英] Is this Rails JSON authentication API (using Devise) secure?
问题描述
我的Rails应用程序使用设计进行验证。它有一个妹妹iOS应用,用户可以使用他们使用的Web应用程序相同的凭据登录到iOS应用。所以,我需要某种形式的API进行身份验证。
在这里类似的问题很多指向的本教程,但它似乎是外的日期,因为 token_authenticatable
模块已被从设计删除,某些行抛出错误。 (我使用的设计3.2.2),我试图根据该教程推出自己的(和这个 ),但我在这没有100%的信心 - 我觉得有可能是一些我误解或错过
首先,以下这个主旨的意见,我添加了一个 authentication_token
文本属性到我的用户
表,以下为 user.rb
:
before_save:ensure_authentication_token高清ensure_authentication_token
如果authentication_token.blank?
self.authentication_token = generate_authentication_token
结束
结束私人的 高清generate_authentication_token
环办
令牌= Devise.friendly_token
打破令牌除非User.find_by(authentication_token:令牌)
结束
结束
然后,我有以下控制器:
api_controller.rb
类ApiController< ApplicationController中
respond_to代码:JSON
skip_before_filter:的authenticate_user! 保护 高清user_params
PARAMS [:用户] .permit(:电子邮件,:密码:password_confirmation)
结束
结束
(请注意,我的 application_controller
的行的before_filter:!的authenticate_user
)
API / sessions_controller.rb
类API :: SessionsController<设计:: RegistrationsController
prepend_before_filter:require_no_authentication,:只=> [:创建 ] 的before_filter:ensure_params_exist respond_to代码:JSON skip_before_filter:verify_authenticity_token 打造高清
build_resource
资源= User.find_for_database_authentication(
电子邮件:PARAMS [:用户] [:邮箱]
)
返回invalid_login_attempt除非资源 如果resource.valid_password?(PARAMS [:用户] [:密码])
sign_in(用户,资源)
渲染JSON:{
成功:真实,
AUTH_TOKEN:resource.authentication_token,
电子邮件:resource.email
}
返回
结束
invalid_login_attempt
结束 DEF破坏
SIGN_OUT(RESOURCE_NAME)
结束 保护 高清ensure_params_exist
返回除非PARAMS [:用户] .blank?
渲染JSON:{
成功:假,
消息:缺少的用户参数
},状态:422
结束 高清invalid_login_attempt
warden.custom_failure!
渲染JSON:{
成功:假,
消息:错误与您的登录名或密码
},状态:401
结束
结束
API / registrations_controller.rb
类API :: RegistrationsController< ApiController
skip_before_filter:verify_authenticity_token 打造高清
用户= User.new(user_params)
如果user.save
渲染(
JSON:Jbuilder.en code做|百灵|
j.success真
j.email user.email
j.auth_token user.authentication_token
结束,
状态:201
)
返回
其他
warden.custom_failure!
渲染JSON:user.errors,状态:422
结束
结束
结束
而在的config / routes.rb中
命名空间:API,默认值为:{格式:JSON}做
devise_for:用户
结束
我不喜欢深一点,我敢肯定有东西在这里,我的未来的自己会回顾和畏缩(通常有)。有些玄乎部分:
首先后,你会发现, API :: SessionsController
从继承制定:: RegistrationsController
,而 API :: RegistrationsController
从 ApiController
(我也有一些其他的控制器,如 API :: EventsController< ApiController
这涉及更标准的REST的东西为我的其他车型不具备与设计多触点)这是一个pretty丑陋的安排,但我想不出让我的访问需要方法API :: RegistrationsController
的另一种方式。我联系到上面的教程有行包括制定::控制器:: InternalHelpers
,但此模块似乎已经在最近版本的设计被删除。
其次,我已经与行 skip_before_filter禁用CSRF保护:verify_authentication_token
。我有我的疑虑,这是否是一个好主意 - 我看到了很多的<一个href=\"http://stackoverflow.com/questions/7600347/rails-api-design-without-disabling-csrf-protection\">conflicting或<一个href=\"http://stackoverflow.com/questions/11008469/are-json-web-services-vulnerable-to-csrf-attacks\">hard要了解有关JSON API的是否容易受到CSRF攻击建议 - 但补充说,行了,我能得到这该死的东西的工作的唯一办法</ P>
第三,我要确保我的理解验证的工作一旦用户在已签署的。说我有一个API调用 GET / API /朋友
返回当前用户的好友列表。据我了解,iOS应用必须得到用户的 authentication_token
从数据库中(这是每一个永远不会改变?用户一个固定值),然后提交与每一项要求,如沿参数 GET / API /朋友?authentication_token = abcdefgh1234
,那么我的 API :: FriendsController
可以做类似 User.find_by(authentication_token:PARAMS [:authentication_token])
来得到CURRENT_USER。难道真是这个简单的,还是我失去了一些东西?
因此,对于任何人谁是设法一路读到这个庞大问题的结束,感谢您的时间!总结:
- 这个登录系统安全吗?或有件事情我已经忽视或误解,例如当谈到CSRF攻击?
- 是我如何验证请求理解一旦用户在正确的签名吗?(见三......以上。)
- 有什么办法这个code,可清洗或取得更好?特别是具有一个控制器的设计难看从
继承制定:: RegistrationsController
和ApiController
其他。
谢谢!
您不想禁用CSRF,我已阅读,人们认为它并不适用于JSON API的一些原因,但是这是一种误解。为了保持启用状态,要进行一些改动:
-
在有服务器端after_filter添加到您的会话控制器:
after_filter:set_csrf_header,只有:[:新:创建]保护高清set_csrf_header
response.headers ['X-CSRF令牌'] = form_authenticity_token
结束这将产生一个道理,把它放在你的会话,并拷贝在选定的行动响应头。
-
客户端(IOS),你需要确保两件事到位。
-
您的客户端需要扫描所有服务器响应此头并保留它,当它被传承下去。
...阿霍德响应对象
//响应可以是NSURLResponse对象,因此转换:
NSHTTPURLResponse * HTT presponse =(NSHTTPURLResponse *)响应;
//抢令牌如果present,确保你有一个配置对象,将其存储在
* NSString的记号= [[HTT presponse allHeaderFields] objectForKey:@X-CSRF令牌];
如果(标记)
[yourConfig setCsrfToken:令牌]; -
最后,您的客户端需要此令牌添加到所有非GET要求它发出的:
...阿霍德您的请求对象
如果(yourConfig.csrfToken&安培;&安培;![request.httpMethod isEqualToString:@GET])
[申请的setValue:yourConfig.csrfToken forHTTPHeaderField:@X-CSRF令牌];
-
最后一块拼图是了解登录时制定,正在使用后两届会议/ CSRF令牌。一个登录流程是这样的:
GET /用户/ sign_in - &GT;
//新动作被调用时,最初的标记设置
//现在回调发送登录表单:
POST /用户/ sign_in&lt;用户名,密码&GT; - &GT;
//创建一个名为动作,令牌复位
//登录成功后,会话和令牌将被替换
//你可以发送验证请求,
My Rails app uses Devise for authentication. It has a sister iOS app, and users can log in to the iOS app using the same credentials that they use for the web app. So I need some kind of API for authentication.
Lots of similar questions on here point to this tutorial, but it seems to be out-of-date, as the token_authenticatable
module has since been removed from Devise and some of the lines throw errors. (I'm using Devise 3.2.2.) I've attempted to roll my own based on that tutorial (and this one), but I'm not 100% confident in it - I feel like there may be something I've misunderstood or missed.
Firstly, following the advice of this gist, I added an authentication_token
text attribute to my users
table, and the following to user.rb
:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Then I have the following controllers:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Note that my application_controller
has the line before_filter :authenticate_user!
.)
api/sessions_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api/registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
And in config/routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
I'm out of my depth a bit and I'm sure there's something here that my future self will look back on and cringe (there usually is). Some iffy parts:
Firstly, you'll notice that Api::SessionsController
inherits from Devise::RegistrationsController
whereas Api::RegistrationsController
inherits from ApiController
(I also have some other controllers such as Api::EventsController < ApiController
which deal with more standard REST stuff for my other models and don't have much contact with Devise.) This is a pretty ugly arrangement, but I couldn't figure out another way of getting access the methods I need in Api::RegistrationsController
. The tutorial I linked to above has the line include Devise::Controllers::InternalHelpers
, but this module seems to have been removed in more recent versions of Devise.
Secondly, I've disabled CSRF protection with the line skip_before_filter :verify_authentication_token
. I have my doubts about whether this is a good idea - I see a lot of conflicting or hard to understand advice about whether JSON APIs are vulnerable to CSRF attacks - but adding that line was the only way I could get the damn thing to work.
Thirdly, I want to make sure I understand how authentication works once a user has signed in. Say I have an API call GET /api/friends
which returns a list of the current user's friends. As I understand it, the iOS app would have to get the user's authentication_token
from the database (which is a fixed value for each user that never changes??), then submit it as a param along with every request, e.g. GET /api/friends?authentication_token=abcdefgh1234
, then my Api::FriendsController
could do something like User.find_by(authentication_token: params[:authentication_token])
to get the current_user. Is it really this simple, or am I missing something?
So for anyone who's managed to read all the way to the end of this mammoth question, thanks for your time! To summarise:
- Is this login system secure? Or is there something I've overlooked or misunderstood, e.g. when it comes to CSRF attacks?
- Is my understanding of how to authenticate requests once users are signed in correct? (See "thirdly..." above.)
- Is there any way this code can be cleaned up or made nicer? Particularly the ugly design of having one controller inherit from
Devise::RegistrationsController
and the others fromApiController
.
Thanks!
You don't want to disable CSRF, I have read that people think it doesn't apply to JSON APIs for some reason, but this is a misunderstanding. To keep it enabled, you want to make a few changes:
on there server side add a after_filter to your sessions controller:
after_filter :set_csrf_header, only: [:new, :create] protected def set_csrf_header response.headers['X-CSRF-Token'] = form_authenticity_token end
This will generate a token, put it in your session and copy it in the response header for selected actions.
client side (iOS) you need to make sure two things are in place.
your client needs to scan all server responses for this header and retain it when it is passed along.
... get ahold of response object // response may be a NSURLResponse object, so convert: NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; // grab token if present, make sure you have a config object to store it in NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"]; if (token) [yourConfig setCsrfToken:token];
finally, your client needs to add this token to all 'non GET' requests it sends out:
... get ahold of your request object if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"]) [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
Final piece of the puzzle is to understand that when logging in to devise, two subsequent sessions/csrf tokens are being used. A login flow would look like this:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
这篇关于这是Rails的JSON API认证(使用设计)安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!