wardencommunity / warden

General Rack Authentication Framework
MIT License
2.48k stars 204 forks source link

clarification with login_as helper #99

Closed mauriciopasquier closed 9 years ago

mauriciopasquier commented 10 years ago

Sorry if this is really obvious, but I need to confirm that I'm not doing something wrong in my setup. I'm confused about the test helper login_as and on_next_request. Is it intended that the user dissapears (i.e. is logged out?) after the next request?

Are these helpers not intended for features/integrations test, where you normally click around and make several requests in a row? In this case, do you see something wrong with using on_request instead of on_next_request?

I don't see the rationale of making it dissapear after one request when you could simply use logout after each test.

Would you accept a patch adding a login_permanently_as helper using on_request? Or maybe a clarification in the README..

mauriciopasquier commented 10 years ago

Well, it seems on_request is more permanent than I thought! I had to clear the hooks accessing Warden::Manager._on_request directly. Maybe a less hackish way is good to have, something like the stuff from Warden.test_reset!

gotjosh commented 10 years ago

I'm also :+1: I ended up monkey patching the TestHelpers module in order to use on_request for my integration tests.

hassox commented 10 years ago

You should test_reset! in your after/teardown phase. The login_as method should be logging you into the session so you should only have to call it once to get it into the cookie jar.

You can see in the code that it just sets the user (which should trigger whatever serialization you have setup) https://github.com/hassox/warden/blob/master/lib/warden/test/helpers.rb#L20

Curious to get a better understanding of the problems you faced.

gotjosh commented 10 years ago

When using capybara to test my Sinatra app I have the something like this to login the user:

before { login_as mock_user }

Then I issue several visit 'url' or click_button 'button_name' and the user is only authenticated for the first request (in this case visit or link/button click).

hassox commented 10 years ago

What does your mock and serializers look like? If you can't serialize the mock_user into and out of the session it's going to seem that you're not being logged in for more than one request.

gotjosh commented 10 years ago

I double checked and my user object is being properly serialized. I'm user the standard user.id for serialization and deserialization.

ledestin commented 9 years ago

I'm trying to use login_as in Capybara tests:

    user = create :user                                                          
    login_as user, scope: :user                                                  

    visit '/'                                                                    
    screenshot_and_save_page                                                     
    visit '/'                                                                    
    screenshot_and_save_page 

The first visit is authenticated, but the second isn't. So, it may work fine for a single HTTP request, but in my case I've got several (I'm using AJAX).

Is there a reason to be using login_as for a single request, given that you request teardown with test_reset!?

hassox commented 9 years ago

The login_as can be thought of as visiting your login page. You only should need to do it once, and after that your session should keep track of what's going on. Please confirm that you session is working correctly, and also that your mock_user is handled by the session serializers.

ledestin commented 9 years ago

Well, the user is serialized under session['warden.user.user.key'] and it looks like this [[2], "$2a$10$12TdWyIwWthkSvpiUV1h9e"] (fields being id and encrypted password). How do I check that session is working correctly?

ledestin commented 9 years ago

There's nothing in the session['warden.user.user.key'] on the second visit though.

hassox commented 9 years ago

That looks like a devise session. If it is, this might help: https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-%28and-RSpec%29

Devise does some clever things with scopes and objects. If it's devise, I'd try using their helper methods, and failing that, don't use a mock for it, use a real object (just to nail down the issue)

ledestin commented 9 years ago
  1. The article is about controller tests, but I'm testing with Capybara, those are feature tests.
  2. Devise login helpers aren't available in Capybara tests.
  3. I'm not using a mock object, it is a real ActiveRecord, created with FactoryGirl.
ledestin commented 9 years ago

There's a Devise howto about Warden and Capybara, so I'm doing it by the book: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

ledestin commented 9 years ago

@gotjosh can you please share what you did? I've tried using on_request and it did nothing for me. Thanks.

gotjosh commented 9 years ago

@ledestin Basically what I did was create a new function called login_permanently_as that does the same thing that Warden does to auth but on test. I'm almost certain this is a bad practice but here you go:

module Warden
  module Test
    module Helpers

      def login_permanently_as(user, opts = {})
        Warden::Manager.on_request do |proxy|
          opts[:event] || :authentication
          proxy.set_user(user, opts)
        end
      end

    end
  end
end
ledestin commented 9 years ago

@gotjosh thanks! May be a bad practice, but it's 3x faster! I've updated it a bit: https://isntabook.mooo.com/faster-feature-spec-logins/

micapam commented 6 years ago

@gotjosh Thanks for that - saved my life!

shlima commented 5 years ago

@gotjosh you have to add following code in this case:

 config.after :each, type: :system do
    Warden::Manager._on_request.clear
  end
jlefley commented 4 years ago

I'm running into the same issue where login_as only provides authentication for one request and I'm confused about why the solution from @gotjosh is needed. I printed the session in the controller handling the requests after calling login_as and it includes the user key but for both requests but the the second is handled as if the user is not authenticated.

Session contents from request made immediately after calling login_as:

session_id: d3a68c592ec2df872c2255a6575ecdc3 warden.user.user.key: [[1], "$2a$04$JfjJsKxuWHEd3vFI6bqF8."]

That first request passes the authentication check and behaves as if the user is signed in.

Then I make the same request again and see this in the session:

session_id: 8d5a8e993050134583753440e4343812 warden.user.user.key: [[1], "$2a$04$JfjJsKxuWHEd3vFI6bqF8."]

That second request does not pass the authentication check and behaves as if the user is not signed in.

I can use the workaround for now but I am unclear about why login_as does not work as intended. This seems to be a somewhat common problem as evidenced by this issue and https://stackoverflow.com/questions/41084378/capybara-resets-devise-session-after-each-request

UPDATE The problem I encountered turned out to be caused by an oversight in my test setup code. I was setting the password attribute after creating the user model and I think Devise computes a new encrypted password when the password attribute is set. So the encrypted password of the user instance in the test differed from what was actually in the database. This caused Warden to set an incorrect salt value based on what was on the user instance, not actually in the database. The mismatch was causing the authentication failure. After fixing that the login_as method works as intended. Thanks!

Here are some other similar issues. Many seem to be related to storing the user in the database.

https://stackoverflow.com/questions/35609694/capybara-not-holding-user-session-after-one-page-visit-rails-4-2 https://stackoverflow.com/questions/49832310/current-user-set-to-nil-on-redirect-session-lost https://stackoverflow.com/questions/18623661/why-is-capybara-discarding-my-session-after-one-event/18846813

dangxuanphuc commented 3 years ago

@gotjosh thank you very much

drewlustro commented 1 year ago

Lost an afternoon to this issue while trying to get Devise/Warden working with system tests. Incredible that @gotjosh's solution is relevant 8 years later.

Given "system tests" are a relatively modern recommendation, most solutions on the web are junk. Devise makes no mention and neither does Warden. Best one gets is misleading documentation on controller or feature specs.

Rails engineers truly are aging out 💀