#250 Authentication from Scratch (revised)
Feb 25, 2012 | 13 minutes | Authentication
Simple password authentication is easy to do with has_secure_password. Here you will learn how to make a complete Sign Up, Log In, and Log Out process as well as restrict access to certain actions.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Thanks for keeping on top of this Ryan. Railscasts is one of the few bills I smile when paying.
feeling the same +1
Feeling the same +9. As in, $9/mo. lol
@Jelkster Totally! I've been watching Railscasts since 2007 and I've learned so much from them. I'm happy to be giving back now. Keep up the awesome work, Ryan!
Hello Ryan,
If this is so simple now, is there a need to use gems like Devise and Sorcery anymore? What is it that you are doing in your consulting work? Please advise.
Thanks.
Bharat
Devise has quite a few options like being able to verify accounts via email, recovery of lost passwords, temporarily blocking accounts with x number of failed log-ins etc.
Thanks for that information regarding devise. I was also wondering why would I want to use it. Though, I'm sure that password resets and account verification wouldn't be that hard to accomplish with this episode as a starting point.
Because of seamless integration with Warden, testing, and a really well-thought out system for managing multiple role contexts.
Any thoughts on revising the episode(s) on Roles? Logging in is important, but I'm about to need to figure out how to allow an administrator to do some destructive actions that a less qualified User would not be permitted to perform. Thanks!
I'm not sure you'd really need to do anything different... Just follow the cancan episode
Thanks for the tip, Daniel. I'm re-writing from scratch (rather than trying to update) an app I did in Rails 2.3.8. Back then, I used the easy_roles gem based on one of Ryan's screencasts from that period. Since so much is new in 3.2.1 (including this excellent built-in authentication functionality) I was reluctant to risk blending old with new. I'll investigate cancan, and I appreciate your suggestion.
What do you not like about the solutions in the previous episodes?
why don't you make the session a singleton resource? it would make sense since i can only have exactly one at a time. this would also fix the
session_path("current")
problem to be justsession_path
+1
Now you should update the CanCan episode as a part 2 to this revised episode. Thanks for putting the previous episodes together into a single lesson.
Ryan,
I had (and others on Overflow) an issue with stale or actually regenerated cookies. For instance. One login and the cookie is set with the :auth_code. If that cookie ever get out of sync with the db via regenerate or db reset the
User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
code will fail. You can simulate this by changing one letter in the :auth_code either in the cookie or db. I agree this should never happen, but when it does it is ugly. This will fix it but I think there is a better solution:
def current_user
begin
@current_user ||= User.find_by_auth_token (cookies[:auth_token]) if cookies[:auth_token]
rescue ActiveRecord::RecordNotFound
@current_user = nil
cookies.delete(:auth_token)
end
@current_user
end
You should keep releasing this screencast like every few months!
BTW- Does anyone have any preferred methods or tools for creating flow of the different user states in a web app, for example, logged in, guest, etc..
Anybody else getting:
undefined local variable or method `current_user'
I don't know why my helper method is not getting loaded..
Paul: Make sure you have the line
helper_method :current_user
in /app/controllers/application_controller.rb
same here. I've added that line in app controller but error is still on my page :
edit:
solved - just add that lines:
private
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
to the
app/helpers/application_helper.rb
;)if still it is not working add
include ApplicationHelper
to your ApplicationController ;)I would really like to see also email account confirmation implementation, Thanks Ryan!
I implemented an account confirmation system using episode 274 as a base.
A straightforward way to avoid the code smells, particularly redundant logic scattered through the code manipulating sessions hash (and perhaps later on the cookies hash) is to use a non-database-backed model (tying back to recent ActiveAttr podcast) that handles Sessions, which persists to whichever hash you find suitable to send it. Thus, your controller can look like:
and then, of course, you can use the form generators on Session.new, instead of hand-coding the, albeit simple form. Note that using simple_form_for, particularly when using something like twitter bootstrap is MUCH easier on the eyes. Finally, your controller code to check for current user simply uses Session.new.on(session).current_user, and now everything is abstracted and DRY in the fat model with skinny controllers.
Even nicer, you can now test the heck out of the Session model in isolation from the controllers, making the acceptance and integration tests a piece of cake, without any need to mess around with hacks to access controller-level code.
Just a thought.
You can simplify the routes you added to this:
It's shorter and cleaner. The as: option is completely unnecessary since rails will add the path helpers without it.
Ryan could have done this because it's more Ruby 1.9 to omit the hash rocket now and personally I think his syntax is looking cleaner.
But you're right that the as: option is unnecessary.
In the users controller, the line
seems to be giving an error because of how the flash notice is assigned. It goes away when switched to this:
Is this a typo or am I doing something else wrong?
Answer: this is an additional syntax available in Ruby 1.9.
I just notice something with this, when you pass a failed login or signup back you lose the /login(/signup) and get given /sessions(/users)
any ideas on how to fix this?
In sessions#create,
instead of
render "new"
try
redirect_to login_url
Someone in the original version of this episode also mentioned dropping the
resources :sessions
line from routes.rbcheers for that, i found the comment here
As a follow up to my previous comment i used the following
in my routes and dropped the
resource :sessions
NB: i still have
resource :users
for the full rest interface therealso with this i had to change the login form to be
form_tag login_path
and my signup form to be
simple_form_for @user, url: signup_path
Does anybody have a good testing strategy for this?
Testing with capybara and rspec , there is a railscasts for that, called "How do I test". Great and simple stuff.
What about controller testing? - I have a bunch of integration tests with capybara which are great, but not sure how to write the controller tests.
The testing question is a valuable one. I am a behavior-driven development addict; Cucumber was the driving force for me to jump ship to Ruby, so I am using Cucumber and RSpec.
I used this railscast as a guide for an app I'm moving to Rails, following the advice to implement auth oneself when new to Rails rather than relying on a gem. Good advice (if sometimes painful!) The sessions controller code worked fine for me with live testing but login tests of bad data generated a missing template error.
Looking at it again, it looked to me as if the create method of the sessions controller code is not asking for the right template when the user enters the wrong info. The error message indicated that it was looking for a
sessions/create
template, which made sense to me, as theelse
branch does not have a render statement (and so it defaults to rendering sessions with the method it is in?). To solve the problem, I addedrender "new"
after setting the flash, as in:Now my sign in features are all passing, yay!
Sorry to be so long-winded, but I'm really new to Rails, so I'm wondering what I got right and what I got wrong in my analysis and solution.
I also wanted to say thank you for RailsCasts. Your service has been invaluable to me as I retool my skills.
I don't think this is a good idea. What if a user changes a password because it was compromised? - You would need to change the member ID to log them out which is a really bad idea.
Aha. I had the same problem but I realized that the ASCIIcast doesn't have it while the main code does!
When i try to open the login form I get this error
Showing /app/views/users/new.html.erb where line #16 raised:
no implicit conversion from nil to integer
Anyone knows how to fix this?
Fantastic, as always!
I note, however, that the user is always redirect to the root_url, which isn't always ideal.
Which is the best method for redirecting a user to the "original" page after they have (possibly unsuccessfully) logged in?
This should also cover static pages - for example, if the user is on an "About" page, then logs in, they should be returned to the "About" page.
Thanks!
Did you find a good way of doing this?
Got this error on signup
undefined method
key?' for nil:NilClass`Any idea ?
Thanks
It seems the error is generated by the line has_secure_password
My Gemfile is OK, don't understand :(
Edit : Ok, it works, I installed brypt-ruby before, so I needed to update my bundle and restart my server !
I have used and been very happy with thoughbot/clearance but decided to follow this and roll my own since I want to use strong_parameters.
One thing I liked in Clearance is a signed_in? method so I added the following to application_controller. It works, just wonder if this is the proper place for it. I know it is very similar to current_user, but I seem to prefer this in certain places.
Here is an example of how I am using it. Note: I use (Haml)[http://www.haml-lang.orl) though indents aren't showing up below.
- if signed_in?
%li= link_to 'Sign out', sign_out_path
- else
%li= link_to 'Sign in', sign_in_path
Great episode!
BTW: Where is the best place to test the
current_user
helper method from the ApplicationController (controller spec vs. view spec)?I want to cover this part as well with specs but having problems to access this method.
Hmm...
Thanks for this! I signed up for Pro just to watch this episode... I think it would be great to include a version of this that would work with a mobile app using backbone. Any advice on how to do that?
I'm new to Rails, and I'm having trouble figuring out how the logout link,
session_path("current")
, is routing to thedestroy
method in the controller?How are people solving upon login failure the URL changes to /sessions/ from the original /sessions/new/ ???
In sessions_controller.rb
the missing part is
render "new"
how can I replace the method find_by_name in rails4? where(:name = 'params[:name]') ?
i have error in session_controller: undefined method `authenticate' =/ anyone help?
Thanks for the nice tutorial.
Thank you very much, nice railscasts, helps a lot to learn and understand.
I would like if someone do some test with capybara, if her/him could post it, it would be really helpful :)
THANKS RYAN!!!
Sorry, maybe I'm not seeing it but what is stopping someone from simply setting a session called user_id manually in their browser. Wouldn't that allow them to steal someones account?
Session cookies are automatically encrypted. Try reading the cookie yourself.
Also see Ruby on Rail Guides, Security
I can log in and log out, but I can also use all the links, add content or delete. So my authentication is not quite working right. My app says I'm successfully logged in, or logged out, but it doesn't matter. Either way there is full access to the rest of the app.
I followed all the direction in this tutorial, and thought it was working until I realized I could still do anything as an unlogged user.
Any idea where I should look to correct this?
Thank you!
Dana:
Make sure you have included the code for basic authorization in application_controller.rb
and then make sure to invoke it in all your controllers:
there is some issue here when us attr_accessible. i included a gem breaks my shopping cart in my rails app. It works fine when i exclude this gem. Any help is appreciated.
@Dee Koder: As of Rails 4, strong parameters are used instead of attr_accessible, read more here: a good answer on stackoverflow and the official ruby api on strong parameters
Chock full of goodness. This pulled together a lot of concepts. One of my favs so far!
When I try to sign up through the user/new.html.erb form, I get a "can't leave password blank error" , I'm putting in a password every time. Anyone know what the problem is?
I am having the same error. Have you fixed it?
Hi Shotsoft, I had the same problem as you, if you remove:
attr_accessor :email, :password, :password_confirmation
from user.rb
and add
to the end of users_controller.rb it should work. See Andreas Bjørn Hassing Nielsen's (above) reply for more information.
Once you've done this you just need to modify the create method so that is has
@user = User.new(user_params)
I was facing the same issue and this solved it. thanks David.
I would like to know if anyone has ever implemented their own registration/login system like what Ryan demonstrates here, and also include OmniAuth for authorizing external APIs like Facebook, etc.?
I have a specification that requires the user to register/login through our system, but they still need external API authorization(account access, data sharing, etc.) this means that even though they can tie in social media account and share information back and forth, they can't use an external API to register for our site. Does anyone have any suggestions? I guess my first question is - is what we need to do compatible with what OmniAuth allows?
Authentication from Scratch : Register Feature using Rails 4.2.1 and Ruby 2.2.2