angularjs - Devise + Angular + Rails 4 : Changing Logged-in user not validating username/password -
...so close desired behavior!
have devise / angularjs / rails 4 project user login/logout working perfectly. sending x-csrf-token , storing user's email in angular cookie user hitting refresh doesn't cause angular lose knowledge of user.
only problem can see this: if logged in admin user then, without logging out... fill in username/login other valid credentials... devise authenticating based on csrf token instead of validating username , password.
therefore, you'll think logged in else, previously-logged-in user. i'm sure pretty simple, can't seem nail it... need force devise/warden always check username/password on create - don't happy csrf token.
here our sessions_controller.rb:
class sessionscontroller < devise::sessionscontroller respond_to :json def create rails.logger.debug("(sessionscontroller.create) ******* ") user = warden.authenticate!(:scope => :user, :recall => "#{controller_path}#failure") rails.logger.debug("(sessionscontroller.create) warden.authenticate ") render :status => 200, :json => { :success => true, :info => "logged in", :user => current_user } end def destroy warden.authenticate!(:scope => :user, :recall => "#{controller_path}#failure") sign_out render :status => 200, :json => { :success => true, :info => "logged out", } end def failure render :status => 401, :json => { :success => false, :info => "login credentials failed" } end def show_current_user warden.authenticate!(:scope => :user, :recall => "#{controller_path}#failure") render :status => 200, :json => { :success => true, :info => "current user", :user => current_user } end end
here application_controller.rb:
class applicationcontroller < actioncontroller::base protect_from_forgery #with: :exception helper_method :require_user, :require_admin after_filter :set_csrf_cookie_for_ng def set_csrf_cookie_for_ng rails.logger.debug("set_csrf_cookie_for_ng called fat:#{form_authenticity_token}") rails.logger.debug("protect_against_forgery = #{protect_against_forgery?}") cookies['xsrf-token'] = form_authenticity_token if protect_against_forgery? end def require_user rails.logger.debug("require_user method in application controller") if !user_signed_in? flash[:notice] = "please sign in!" redirect_to new_user_session_path end end def is_admin_user return false if current_user.nil? return current_user.admin? end def is_any_user return false if current_user.nil? return user_signed_in? end def verify_admin_user unless is_admin_user rails.logger.debug("unauthorized - must admin") respond_to | format | format.json { render :json => [], :status => :unauthorized } end end end def verify_any_user unless user_signed_in? rails.logger.debug("unauthorized - must logged-in") respond_to | format | format.json { render :json => [], :status => :unauthorized } end end end protected def verified_request? rails.logger.debug("verified_request called f_a_t:#{form_authenticity_token}. x-xsrf-token:#{request.headers['x-xsrf-token']}") super || form_authenticity_token == request.headers['x-xsrf-token'] end end
routes.rb:
devise_for :users, :controllers => {:sessions => "sessions"}
finally, on angularjs side, our httpprovider:
.config(["$httpprovider", ($httpprovider) -> $httpprovider.defaults.headers.common["x-csrf-token"] = $("meta[name=csrf-token]").attr("content")
here see going in logs if logged-in admin, attempt "login-over" non admin... note "user 1" admin logged-in already.
started post "/users/sign_in.json" 127.0.0.1 @ 2014-06-02 23:21:25 -0500 processing sessionscontroller#create json parameters: {"user"=>{"email"=>"nonadmin@example.com", "password"=>"[filtered]"}, "session"=>{"user"=>{"email"=>"nonadmin@example.com", "password"=>"[filtered]"}}} verified_request called f_a_t:p2fzxg/qwooenp0ttme8urxom9tzwh1bo5y28j8rthc=. x-xsrf-token:p2fzxg/qwooenp0ttme8urxom9tzwh1bo5y28j8rthc= (sessionscontroller.create) ******* user load (0.5ms) select "users".* "users" "users"."id" = 1 order "users"."id" asc limit 1 (sessionscontroller.create) warden.authenticate set_csrf_cookie_for_ng called fat:p2fzxg/qwooenp0ttme8urxom9tzwh1bo5y28j8rthc= protect_against_forgery = true completed 200 ok in 3ms (views: 0.8ms | activerecord: 0.5ms)
appreciate help!
devise/warden (by default) uses session/cookie based authentication, not csrf token thats authenticating session.
you have couple of options going forward
1, disable login form when angular knows user logged in
2, explicitly sign user out (by calling sign_out
) in create action
3, (preferred) don't use session authentication on api. api should stateless. mean server shouldn't know whether user 'logged in'. should sending authentication token every request (not relying on session). here http://www.soryy.com/ruby/api/rails/authentication/2014/03/16/apis-with-devise.html nice write up.
the basics of token based authentication are:
- angular sends username , password server
- server authenticates username , password, generates 'token' user , sends token angular
- angular stores token (in local storage / cookie)
- with each new request (eg: /api/private_thing.json) angular sends 'token' (in header or parameter)
- server checks token belongs user record permission view 'private_thing'
good luck
update
if going go option 2 change create action to:
def create sign_out if is_any_user render :status => 200, json: { success: true, info: "logged in", user: current_user } end
Comments
Post a Comment