devise: current_user should not perform sign in by default
Environment
- Ruby [2.5.1]
- Rails [5.2]
- Devise [4.5]
Current behavior
It is very hard to prevent sign in with Devise. If you subclass SessionsController to override create, even if you don’t allow super to run, any template code that tries to check current_user will automatically run warden’s authentication, which signs in the user.
Expected behavior
Users should only be signed in after an invocation of sign_in.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 11
- Comments: 37 (15 by maintainers)
Faced this same problem today when working on custom implementation of 2FA. I understand @tegon’s point on backwards compatibility, that’s indeed too big of a change nowadays. But I do feel such implicit sign-in is not quite right either.
So I ended up adding this simple instance method override logic to
application_controller.rb:This way it omits implicit sign-in while keeping all the existing logic intact. Hope that helps!
Respectfully, that is silly -
current_userreturnsnilas a valid return type in case of auth returning nothing, and you are suggesting that users should never learn to examine this return type. Many Devise programmers across the internet have learned to usecurrent_userreturningnilas a pattern for conditionally doing things when a user is not logged in. Many users write code likeif current_user&.some_flag?(and that is a good thing).Tutorials like this one will commonly contain (apparently mistaken) refrains like:
Even more simply, though, the whole design here is somewhat crazy. These helpers are defined by Devise:
Of these, both
authenticate_user!andcurrent_userperform logins. There is literally no way to access the current user without potentially performing a login. This is a huge design flaw and should be corrected. If a new major version needs to be cut, so be it.Your statement gives me the impression that you may be confused - current_user’s explicit purpose is to tell you whether or not the user is signed in. I am saying that it should NOT be a secret, double purpose to perform a login if one is not found, which it does now. Application code everywhere in the app must rely on current_user (or equivalent helper, user_signed_in?) in order to decide whether the user is authorized to view the current page, etc.
Logins should only explicitly be created in controller code, not automatically by helpers.
That behavior is very surprising, and I spent a significant amount of time debugging an issue because of that
Hi everyone, thanks for all points raised in this discussion.
First of all, I want to say that I would also prefer an explicit behavior rather than an implicit one, which is what’s happening in
#current_user. But I will also say that this is a very common pattern in Ruby on Rails applications - I’ve seen it everywhere in my career - it’s the memoization pattern, where people try to “cache” side effects in a method with an instance variable that has the same name. I’ve seen people use in database queries, network requests and so on. So, even though I’d prefer an explicit behavior, I think since it’s documented, it’s ok to keep it as it is. Most people are used to, and relying on this behavior by now, so changing would make it a pain to upgrade - even in a major version.There is something that should be addressed though: the
#user_signed_in?method, that is mentioned on the documentation as the way to check whether the user is authenticated or not, is actually performing the sign in too. You can see more about it here. We will fix that one in the next major version.As for the use cases mentioned before, I think overriding the sessions controller is not the best alternative. The two-factor auth, for example, is another way of authenticating a user, so a Warden strategy is the best way to solve this problem, as you mentioned already here before.
That being said I think it’s better to close this issue and continue with #5013 where we are going to fix
#user_sign_in?. What do you guys think?@lacostenycoder we ended up adding a custom warden strategy. Looks something like:
Okay, so let’s say you have a SessionsController with the goal to prevent logins conditionally:
If, say, the view for
some_other_urlcallscurrent_user, the the user will be logged in anyway. This seems bad.Faced the same problem that the
current_userperforms authentication.Tried workaround from @pokrovskyy (here), but i got this error instead:
So I ended up having this workaround instead
The boolean method alone is not very helpful for porting code to correct it. I’d rather have a version of current_user that does not login, but I suppose I could add it myself as a helper.
@tegon
current_user(no_login_attempt: true)ortemp_user.we just add functionality and we won’t break anything
I don’t follow your reasoning. Why is memoization the relevant pattern here? The problem isn’t that
current_usercaches its results, its that it performs login.