does has_secure_password works also with mongoid?
I still get "undefined local variable or method `has_secure_password' for User:Class (NameError)". This with Rails 3.1.0.rc4 and mongoid 2.0.2
As I read somewhere else "Any ActiveModel based model will get this functionality. (ActiveRecord, Mongoid)".
Ryan. I successfully created a search resource for my application, as I have about 10 different form elements. I am battling with passing the page number directly into the model? How do I go about this? I can obviously store the page number as an instance variable in the controller but how do I inject it into the model where I call the paginate method? Hope you can help. Cheers.
Hey Ryan, thank you for the explanation. That makes a lot of sense. Still though, after reading your comment and seeing the episode I personally would prefer to go the UsersController way, having a special "reset_password" action.
I haven't tested this yet so I don't know if this is correct, but I guess this is all that is needed to get it working in the routes, which isn't all too complex and requires only three extra lines (two if you already have the :users resources):
routes.rb
ruby
resources :password_resets
resources :usersdo
get "reset_password", on::memberend# /users/:password_reset_token/reset_password
or as an alternative:
ruby
resources :password_resets
get "reset_password/:id" => "users#reset_password"# /reset_password/:password_reset_token
UsersController
ruby
defreset_password@user = User.find_by_password_reset_token!(params[:id])
if@user.password_reset_sent_at < 2.hours.ago
redirect_to new_password_reset_path, alert:"Password reset has expired."elsif@user.update_attributes(params[:user])
redirect_to root_url, notice:"Password has been reset!"else
render :editendend
Ryan, What if you have a model where none of the attributes should be modified by the user (an e-commerce app for example where the admin only can add products)? You say that attr_protected leaves a vulnerability and should be avoided...so what is the alternative? Should I simply have a line for attr_accessible and assign no attributes to it?
Thanks a lot Ryan,
Your approach does not allow a user to remember me from multiple machines. So I guess it worth to mention that. Also it worth to mention that probably storing the tokens in a Rails.cache is more scalable and durability can be achieved if the cache is backed by a durable cache (like membase)
I am new also to rails. Is there a way to generate a unique token with a key (like the user id), which would ensure its uniqueness, which would prevent you from having to check if it already exist in the user table.
I'd just like to point out, that Rails > 3.0.4 requires you to add an additional method to authentications_controller.rb:
ruby
protected
# This is necessary since Rails 3.0.4# See https://github.com/intridea/omniauth/issues/185# and http://www.arailsdemo.com/posts/44defhandle_unverified_requesttrueend
Is it possible to use this with formtastic gem? Right now I'm using formtastic, for my forms (think that's why it gives me back error "wrong number of arguments (0 for 1)", when I add :authors_tokens field to my book form).
One more great article. But I prefer using authlogic or device to achieve those results as those gems have a lot of features explained here and much more. And they're covered with tests what is important!
I didn't use scaffolding in this episode, however I did use several generators (controller, mailer, migration). I think most Rails developers use these generators.
That said, I do scaffolding in my everyday development through Nifty Generators. I encourage everyone to create their own scaffold generators to improve their workflow.
I think scaffolding just gets a bad rap because the built-in scaffold generator is near useless in everyday Rails development.
I do hope to incorporate more of my development workflow into these screencasts. Maybe do some TDD at the beginning. We'll see.
That's a good point. When I mentioned the edit action not being truly RESTful I am not hiding a better solution, but more pointing out I am intentionally not following REST here. I considered a couple alternatives when preparing this episode.
What I consider the "proper" way to do this would be to have two controllers/resources. One being PasswordResetRequests with new/create actions, and one being "UserPasswords" with edit/update actions. But two controllers seem overboard for this one feature.
Alternatively we could handle the editing of the password in the Users controller, after all it is the User model we are working on, but if we add a way to edit the user profile then there is a conflict here. Suddenly the edit/update actions would need to have several "if" conditions determining if it is editing the profile or resetting the password. Not only that but it is mixing the password reset feature into other parts of the app. This would quickly get messy.
REST is actually more about the URLs than it is controllers, so we could keep everything in the same controller but add custom URLs. However, this would require 4 complex lines in the routes file instead of the simple resources :password_resets. I do not like that solution either.
So, we're left with leaving everything in a PasswordResets controller which follows the REST pattern but it is not truly RESTful because it is working on two separate resources. Still I consider it to be the best solution for this problem. Maybe there are ways the Rails router itself can be improved to handle these situations.
Nice to see you active in the comments section as well. Wish you dropped by a little later to allow me to add this question to my reply, but here goes:
In the episode at 8:13 you talk about the URL the user uses to reset his/her password and that it isn't the most elegant RESTful route, but "it will work for us here".
I love (almost) all the screencasts you publish, and I watch every one of them at the start of the week (go self employed!) but I've noticed you've taken these "quick and dirty routes" a couple of more times in the past, and every time I hear you say that I get a nasty feeling, that I am following along writing some great code, and suddenly we make a shortcut and you leave me (us?) high and dry, wondering what I should do to make it actually be the best RESTful way to handle this.
On internal code (code that is never shown to the outer world) I don't mind quick'n'dirty techniques from time to time, if it helps me move on so I can come back to it later, but crucial parts (database layout) and code that is shown to the outside world (routing), I like to make it as good as possible.
Now this isn't criticism, instead I hope you take this as positive feedback. And perhaps you could elaborate on ways to improve the route at 8:13 for this screencast.
Thank again, and obviously we all have to try things ourself, so I'll definitely be trying to improve that piece of code.
Thanks for the feedback. I will try to do both because I think both have their place. I too try to reduce the number of gems I use in a project because having many dependencies has bitten me in the past.
Fantastic episode, Ryan. I am an experienced developer but a newbie to Rails. I enjoyed this "from scratch" episode immensely.
Do you any episodes where you go without scaffolding? Any where you plug in the different parts like controller, model(is this always necessary), view, custom validation, helpers by hand and as late as possible?
I see many blog posts about how scaffolding is not the recommended way but haven't found much guidance on how to do this from scratch.
Also, I am also very interested in BDD (behaviour driven development) and am hoping you can give some guidance on how to do that.
Very good point. If you're giving an error that the email address is registered then there is no point in making the password reset message generic. In that case you might as well go with a more user friendly approach and state that the email address was not found.
I mentioned in the screencast that you may want to change that experience depending on the app.
I too prefer Omniauth for authentication, but not everyone wants to use external services for auth. Even when using Omniauth it's good to provide a fallback for username/password if users don't want to link the account to twitter/facebook.
Thanks for the feedback regarding gems vs. from scratch. I try to cover both because I think they both have their place. There are many cases where gems don't quite fit and require so much customization that the dependency is not worth it. Authentication is often that way for me.
I do like it that Authlogic does not try to take over views, but I think there are better solutions out there. It always bothered me how much it knows about the current request/session, and it felt a little over-complicated.
Great episode. And I too want to put my vote down for more "from scratch" episodes.
I love the gem community and all the Github repos out there, and while I sometimes use these screencasts to find new "gems" amongst the gems, I mainly use it to learn and follow along while I write the same code, in my own style.
Well I feel bad because I have been providing feedback that the screencasts have focused too much on gems. I like to see things from scratch, it helps become a better coder. Omniauth is a perfect example, sure you can just plug it into devise, but what if you need to use external services separate from login - most people would be stuck because they've relied so heavily on gems to do this kind of work. All I can say is we have a difference of opinion, i just wanted mine to be heard because I like a variety of screencasts, sometimes about specific code, and sometimes about gems.
Ryan,
Nice Podcast as always! You might want to consider doing a podcast on Two factor Authentication since security has been in the media lately and on our minds. https://moocode.com/posts/2-two-factor-authentication-with-rails is a nice tutorial but I think you could make it into a great podcast! Just a though thou. ;0)
The authentication gem sorcery claims to be a "stripped-down, bare-bones authentication library" that lets you add common auth functionality from the library to your own controllers.
I haven't used it yet, but I plan to give it a shot on my next project.
@Javi, the idea is that authentication requires two parts - a username and password. However, there are tools that allow you to brute force attacks on a login form.
If the flash/error message changes depending on whether the username is valid, it will take far less time for a hacker to determine a valid username. Once a valid username is found, it becomes a simple attack (unless you implement some kind of failsafe in your code for multiple incorrect password attempts)
Mathematically, imagine it takes a computer 10 hours to brute-force a field of 64 characters (it's not, but I want easy math). If error messages don't reveal that a given username exists, the computer is required to compute 10x10 = 100 hours (for each username generated, it needs to produce passwords as well). However, if the hacker can determine a valid username first (10 hours), and then only brute-force the password (another 10 hours), it only takes 20 hours to hack the site.
It's not "useless", but it's not full-proof either.
This is a really great tutorial, especially in terms of security and understanding authorization, however, I'd just like to caution everyone about rolling their own authentication.
Pre oAuth, every site required it's own username/email and password. This results in countless accounts on multiple servers. However, now, it's easy to authenticate with Facebook, Amazon, or (like Railscasts) github.
I'd just like to encourage developers to consider using an oAuth mechanism in their next project, if deemed fit. Do we really need another account? :)
I know I'm asking this a long time after the screencast was made, but maybe I'll get lucky:
After struggling a bit with having this run with my ruby install, I have the following problem:
simplecov only check for the coverage of the files I have in lib/, even if rspec is indeed running all the tests (so testing classes and methods for all files in app/*/.rb).
Any idea what I need to configure in order to get this to run properly?
At 5:45 you say something about not letting bad guys know if an email is already registered.
I've heard many people arguing that and many sites implementing it. My experience so far:
It confuses legitimate users who mistype their email addresses.
It's useless against bad guys.
Why is it useless? Because on every site I've tried, you can just try to register with a given email address and it gives an error saying "email already registered" if it is.
I like episodes about gems much more: just a little piece of code, and great result. This time i didn't see that. Instead, we got 5 times more code to remember - that's hard for me. :)
Still, there's no need to show everything in things-from-scratch, it's complicated, boring, and will never be the same for other case.
In my opinion, for modern small website we should forget about registering user emails - only omniauth - it's fast, it's handy, but i don't know a simple way to get it up and running on rails yet. For example: devise + omniauth still requires me to know user email. I don't really need it. I'd love to come to website, press "Add comment" button, choose authentication provider, and that's it (just like on railscasts, btw)
Anyways, i'll come back here, to this Episode, whenever i will need authentication from scratch.
For anyone who is still using ruby 1.8.7 for rails 3.0.5
SecureRandom.urlsafe_base64 will generate a method not found error. I used SecureRandom.hex as a substitute and it worked. Just thought I'd share in case this helps anyone...
Nice work. Again for those who didn't understand, this screencast should be consider as an introduction to authentication. The purpose of it is not to define all security hole and patch them.
The Devise source code is very good at this. The engine cover a lot of advance features. I suggest everyone to have a look at it and learn from it.
Secure an application is not an easy task to accomplish. I suggest everyone to create a checklist and verify there projects against it.
I think that the user record should probably store their log-in OS/Browser checksum to validate any returning users.
I find users often want to log-in through multiple devices at the same time. Also if one can steal the cookie it would not be difficult to spoof their user agent.
I think it depends on the app, if you are making a banking site then security is much more critical and then you probably shouldn't listen to me. :)
I also agree with @Iain and @introspectif that there are too many security loopholes to remember to close each time to build from scratch.
Are there any specific security loopholes you see that would be difficult to remember? The most common security issue is hashing the password which is now handled nicely in Rails 3.1 with has_secure_password.
Also I would suggest adding an index on the email column of the user model since every time someone logs in you are performing an
Good point! The auth_token should be indexed as well.
However, I think you have too much logic inside te password_resets_controller. Especially the 2.hours part.
I agree that should probably go somewhere else, maybe in the User model or a separate config. I would hesitate to create a PasswordReset class just for this. True that it might fit the RESTful paradigm when there is a resource backing it, but I don't think the end result would be clearer or easier to read.
And I agree with @introspectif that Devise is childishly easy to customize. So much so that I would never write my own authentication. There are too many security pitfalls to remember.
Good point about security. I personally feel Devise gets messy when needing to customize the behavior, however this may be just a personal opinion. I would love to see something lower level that can handle all of the security measures without taking over controllers. Something along the lines of Authlogic but which followed MVC a little better.
You're right, I prefer using custom logins and authentication over Devise or other methods. I just feel "out of control" when using a plug for something as secure and important as my login.
You're right, it is easy to customize Devise views. My point being that if you're going to customize this anyway, I feel it's a good portion of the work involved in adding these features from scratch. I also find I often need to customize more than this in Devise, such as overriding certain controller actions, and then things get messy.
Great point. You can store the user id in a signed cookie instead of generating a custom token. However for permanent cookies I prefer to keep it separate so it gives you more control over when to expire the session. For example you can reset the auth_token every time someone resets the password, or maybe every time someone logs in to automatically log-out previous ones. There's a lot more flexibility here.
It is also possible to change when the session cookie expires, but I think it's better to stick with a separate cookie.
One problem with your method is that, while the authentication tokens cannot be guessed they can be duplicated and used on another system. It's close to session fixation. I think that the user record should probably store their log-in OS/Browser checksum to validate any returning users.
At least then it would be harder to steal a session.
Hi Ryan
does has_secure_password works also with mongoid?
I still get "undefined local variable or method `has_secure_password' for User:Class (NameError)". This with Rails 3.1.0.rc4 and mongoid 2.0.2
As I read somewhere else "Any ActiveModel based model will get this functionality. (ActiveRecord, Mongoid)".
Thanks for the great job!
Ryan. I successfully created a search resource for my application, as I have about 10 different form elements. I am battling with passing the page number directly into the model? How do I go about this? I can obviously store the page number as an instance variable in the controller but how do I inject it into the model where I call the paginate method? Hope you can help. Cheers.
Thanks guys! That helped a lot! :)
Hey Ryan, thank you for the explanation. That makes a lot of sense. Still though, after reading your comment and seeing the episode I personally would prefer to go the UsersController way, having a special "reset_password" action.
I haven't tested this yet so I don't know if this is correct, but I guess this is all that is needed to get it working in the routes, which isn't all too complex and requires only three extra lines (two if you already have the :users resources):
routes.rb
or as an alternative:
UsersController
Ryan, What if you have a model where none of the attributes should be modified by the user (an e-commerce app for example where the admin only can add products)? You say that attr_protected leaves a vulnerability and should be avoided...so what is the alternative? Should I simply have a line for attr_accessible and assign no attributes to it?
Thanks for the screencast!
Thanks a lot Ryan,
Your approach does not allow a user to remember me from multiple machines. So I guess it worth to mention that. Also it worth to mention that probably storing the tokens in a Rails.cache is more scalable and durability can be achieved if the cache is backed by a durable cache (like membase)
I am new also to rails. Is there a way to generate a unique token with a key (like the user id), which would ensure its uniqueness, which would prevent you from having to check if it already exist in the user table.
Geocoder is a great gem and works perfectly fine for me until
I added will_paginate to paginate the results using .near()
It never gives me the correct number of items on the view which is really weird. Any help would be appreciated, thanks!
I'd just like to point out, that Rails > 3.0.4 requires you to add an additional method to
authentications_controller.rb
:Thanks for the tip ... I was noticing the same problem ... Every article I displayed on a single page had its own
wrapper ...
Why to_sql function works like this:
Comment.root.to_sql
=> "SELECT
comments
.* FROMcomments
WHEREcomments
.parent_id
IS NULL ORDER BY created_at desc"Comment.includes(:user).to_sql
=> "SELECT
comments
.* FROMcomments
"in the second example real sql query should differs from result shown by to_sql function
Is it possible to use this with formtastic gem? Right now I'm using formtastic, for my forms (think that's why it gives me back error "wrong number of arguments (0 for 1)", when I add :authors_tokens field to my book form).
In case it helps anyone...
To make it work in Rails 2.3, I had to include jQuery before jQuery-Token javascript apart from changing the version specific helpers...
One more great article. But I prefer using authlogic or device to achieve those results as those gems have a lot of features explained here and much more. And they're covered with tests what is important!
Hey Ryan. If we're in the topic of authentication - is there any chance You could make an episode about creating oAuth provider?
There's no good tutorial out there about this.
Great episode btw! :)
I didn't use scaffolding in this episode, however I did use several generators (controller, mailer, migration). I think most Rails developers use these generators.
That said, I do scaffolding in my everyday development through Nifty Generators. I encourage everyone to create their own scaffold generators to improve their workflow.
I think scaffolding just gets a bad rap because the built-in scaffold generator is near useless in everyday Rails development.
I do hope to incorporate more of my development workflow into these screencasts. Maybe do some TDD at the beginning. We'll see.
That's a good point. When I mentioned the edit action not being truly RESTful I am not hiding a better solution, but more pointing out I am intentionally not following REST here. I considered a couple alternatives when preparing this episode.
What I consider the "proper" way to do this would be to have two controllers/resources. One being PasswordResetRequests with new/create actions, and one being "UserPasswords" with edit/update actions. But two controllers seem overboard for this one feature.
Alternatively we could handle the editing of the password in the Users controller, after all it is the User model we are working on, but if we add a way to edit the user profile then there is a conflict here. Suddenly the edit/update actions would need to have several "if" conditions determining if it is editing the profile or resetting the password. Not only that but it is mixing the password reset feature into other parts of the app. This would quickly get messy.
REST is actually more about the URLs than it is controllers, so we could keep everything in the same controller but add custom URLs. However, this would require 4 complex lines in the routes file instead of the simple
resources :password_resets
. I do not like that solution either.So, we're left with leaving everything in a PasswordResets controller which follows the REST pattern but it is not truly RESTful because it is working on two separate resources. Still I consider it to be the best solution for this problem. Maybe there are ways the Rails router itself can be improved to handle these situations.
Nice to see you active in the comments section as well. Wish you dropped by a little later to allow me to add this question to my reply, but here goes:
In the episode at 8:13 you talk about the URL the user uses to reset his/her password and that it isn't the most elegant RESTful route, but "it will work for us here".
I love (almost) all the screencasts you publish, and I watch every one of them at the start of the week (go self employed!) but I've noticed you've taken these "quick and dirty routes" a couple of more times in the past, and every time I hear you say that I get a nasty feeling, that I am following along writing some great code, and suddenly we make a shortcut and you leave me (us?) high and dry, wondering what I should do to make it actually be the best RESTful way to handle this.
On internal code (code that is never shown to the outer world) I don't mind quick'n'dirty techniques from time to time, if it helps me move on so I can come back to it later, but crucial parts (database layout) and code that is shown to the outside world (routing), I like to make it as good as possible.
Now this isn't criticism, instead I hope you take this as positive feedback. And perhaps you could elaborate on ways to improve the route at 8:13 for this screencast.
Thank again, and obviously we all have to try things ourself, so I'll definitely be trying to improve that piece of code.
Thanks for the feedback. I will try to do both because I think both have their place. I too try to reduce the number of gems I use in a project because having many dependencies has bitten me in the past.
Fantastic episode, Ryan. I am an experienced developer but a newbie to Rails. I enjoyed this "from scratch" episode immensely.
Do you any episodes where you go without scaffolding? Any where you plug in the different parts like controller, model(is this always necessary), view, custom validation, helpers by hand and as late as possible?
I see many blog posts about how scaffolding is not the recommended way but haven't found much guidance on how to do this from scratch.
Also, I am also very interested in BDD (behaviour driven development) and am hoping you can give some guidance on how to do that.
Thanks for your contribution
-Venu
Very good point. If you're giving an error that the email address is registered then there is no point in making the password reset message generic. In that case you might as well go with a more user friendly approach and state that the email address was not found.
I mentioned in the screencast that you may want to change that experience depending on the app.
I too prefer Omniauth for authentication, but not everyone wants to use external services for auth. Even when using Omniauth it's good to provide a fallback for username/password if users don't want to link the account to twitter/facebook.
Thanks for the feedback regarding gems vs. from scratch. I try to cover both because I think they both have their place. There are many cases where gems don't quite fit and require so much customization that the dependency is not worth it. Authentication is often that way for me.
Sorcery has been on my list to check out, but I haven't yet. Thanks for the reminder!
I do like it that Authlogic does not try to take over views, but I think there are better solutions out there. It always bothered me how much it knows about the current request/session, and it felt a little over-complicated.
Great episode. And I too want to put my vote down for more "from scratch" episodes.
I love the gem community and all the Github repos out there, and while I sometimes use these screencasts to find new "gems" amongst the gems, I mainly use it to learn and follow along while I write the same code, in my own style.
Thanks again!
Well I feel bad because I have been providing feedback that the screencasts have focused too much on gems. I like to see things from scratch, it helps become a better coder. Omniauth is a perfect example, sure you can just plug it into devise, but what if you need to use external services separate from login - most people would be stuck because they've relied so heavily on gems to do this kind of work. All I can say is we have a difference of opinion, i just wanted mine to be heard because I like a variety of screencasts, sometimes about specific code, and sometimes about gems.
Checkout https://github.com/rejeep/authlogic-password-reset-tutorial for reset with Authlogic!
Ryan,
Nice Podcast as always! You might want to consider doing a podcast on Two factor Authentication since security has been in the media lately and on our minds. https://moocode.com/posts/2-two-factor-authentication-with-rails is a nice tutorial but I think you could make it into a great podcast! Just a though thou. ;0)
The authentication gem sorcery claims to be a "stripped-down, bare-bones authentication library" that lets you add common auth functionality from the library to your own controllers.
I haven't used it yet, but I plan to give it a shot on my next project.
https://github.com/NoamB/sorcery
and, back to parent post, imagine - i'm a hacker, and tryng to register existing email, it shows an error, so now i know valid email to attack.
Looks like @marqueymarc was able to resolve his issue by explicitly adding
json_pure
andjson
to the Gemfile.reference: http://stackoverflow.com/questions/6636929/railscast-geocoding-missing-jsonparsererror-constant
@Javi, the idea is that authentication requires two parts - a
username
andpassword
. However, there are tools that allow you to brute force attacks on a login form.If the flash/error message changes depending on whether the username is valid, it will take far less time for a hacker to determine a valid username. Once a valid username is found, it becomes a simple attack (unless you implement some kind of failsafe in your code for multiple incorrect password attempts)
Mathematically, imagine it takes a computer 10 hours to brute-force a field of 64 characters (it's not, but I want easy math). If error messages don't reveal that a given username exists, the computer is required to compute 10x10 = 100 hours (for each username generated, it needs to produce passwords as well). However, if the hacker can determine a valid username first (10 hours), and then only brute-force the password (another 10 hours), it only takes 20 hours to hack the site.
It's not "useless", but it's not full-proof either.
This is a really great tutorial, especially in terms of security and understanding authorization, however, I'd just like to caution everyone about rolling their own authentication.
Pre oAuth, every site required it's own username/email and password. This results in countless accounts on multiple servers. However, now, it's easy to authenticate with Facebook, Amazon, or (like Railscasts) github.
I'd just like to encourage developers to consider using an oAuth mechanism in their next project, if deemed fit. Do we really need another account? :)
You'd be better doing something like:
User.find_by_email(email.downcase)
You're making SQL do too much work, and you can't leverage on indexing when using aggregate functions.
Ryan, I agree with you.
I used to use Devise. But then I realised I spend way more time trying to customise it than just implementing it from scratch.
This is especially true when you need to do something that Devise doesn't support "out of the box".
I know I'm asking this a long time after the screencast was made, but maybe I'll get lucky:
After struggling a bit with having this run with my ruby install, I have the following problem:
simplecov only check for the coverage of the files I have in lib/, even if rspec is indeed running all the tests (so testing classes and methods for all files in app/*/.rb).
Any idea what I need to configure in order to get this to run properly?
Cheers, and thanks for the video!
Nothing wrong with hex, but if you want base64, you could do this:
What's with Authlogic anyhow? I always felt it was the better level of abstraction compared to Devise.
Hi, Ryan. Just an observation.
At 5:45 you say something about not letting bad guys know if an email is already registered.
I've heard many people arguing that and many sites implementing it. My experience so far:
Why is it useless? Because on every site I've tried, you can just try to register with a given email address and it gives an error saying "email already registered" if it is.
I like episodes about gems much more: just a little piece of code, and great result. This time i didn't see that. Instead, we got 5 times more code to remember - that's hard for me. :)
Still, there's no need to show everything in things-from-scratch, it's complicated, boring, and will never be the same for other case.
In my opinion, for modern small website we should forget about registering user emails - only omniauth - it's fast, it's handy, but i don't know a simple way to get it up and running on rails yet. For example: devise + omniauth still requires me to know user email. I don't really need it. I'd love to come to website, press "Add comment" button, choose authentication provider, and that's it (just like on railscasts, btw)
Anyways, i'll come back here, to this Episode, whenever i will need authentication from scratch.
For anyone who is still using ruby 1.8.7 for rails 3.0.5
SecureRandom.urlsafe_base64 will generate a method not found error. I used SecureRandom.hex as a substitute and it worked. Just thought I'd share in case this helps anyone...
Nice work. Again for those who didn't understand, this screencast should be consider as an introduction to authentication. The purpose of it is not to define all security hole and patch them.
The Devise source code is very good at this. The engine cover a lot of advance features. I suggest everyone to have a look at it and learn from it.
Secure an application is not an easy task to accomplish. I suggest everyone to create a checklist and verify there projects against it.
I find users often want to log-in through multiple devices at the same time. Also if one can steal the cookie it would not be difficult to spoof their user agent.
I think it depends on the app, if you are making a banking site then security is much more critical and then you probably shouldn't listen to me. :)
Are there any specific security loopholes you see that would be difficult to remember? The most common security issue is hashing the password which is now handled nicely in Rails 3.1 with
has_secure_password
.Good point! The
auth_token
should be indexed as well.I agree that should probably go somewhere else, maybe in the User model or a separate config. I would hesitate to create a PasswordReset class just for this. True that it might fit the RESTful paradigm when there is a resource backing it, but I don't think the end result would be clearer or easier to read.
Good point about security. I personally feel Devise gets messy when needing to customize the behavior, however this may be just a personal opinion. I would love to see something lower level that can handle all of the security measures without taking over controllers. Something along the lines of Authlogic but which followed MVC a little better.
Awesome episode as usual.
You're right, I prefer using custom logins and authentication over Devise or other methods. I just feel "out of control" when using a plug for something as secure and important as my login.
Keep up the great work!
You're right, it is easy to customize Devise views. My point being that if you're going to customize this anyway, I feel it's a good portion of the work involved in adding these features from scratch. I also find I often need to customize more than this in Devise, such as overriding certain controller actions, and then things get messy.
Great point. You can store the user id in a signed cookie instead of generating a custom token. However for permanent cookies I prefer to keep it separate so it gives you more control over when to expire the session. For example you can reset the
auth_token
every time someone resets the password, or maybe every time someone logs in to automatically log-out previous ones. There's a lot more flexibility here.It is also possible to change when the session cookie expires, but I think it's better to stick with a separate cookie.
Ryan,
One problem with your method is that, while the authentication tokens cannot be guessed they can be duplicated and used on another system. It's close to session fixation. I think that the user record should probably store their log-in OS/Browser checksum to validate any returning users.
At least then it would be harder to steal a session.
I also agree with @Iain and @introspectif that there are too many security loopholes to remember to close each time to build from scratch.
Also I would suggest adding an index on the email column of the user model since every time someone logs in you are performing an
user = User.find_by_email(params[:email])