RailsCasts Pro episodes are now free!

Learn more or hide this

Recent Comments

Avatar

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!

Avatar

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.

Avatar

Thanks guys! That helped a lot! :)

Avatar

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 :users do
  get "reset_password", on: :member
end

# /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
def reset_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 :edit
  end
end
Avatar

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!

Avatar

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)

Avatar

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.

Avatar

Geocoder is a great gem and works perfectly fine for me until
I added will_paginate to paginate the results using .near()

ruby
@jobs = Job.near(@address, @miles).paginate(:page => params[:page], :per_page => 10)

It never gives me the correct number of items on the view which is really weird. Any help would be appreciated, thanks!

Avatar

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/44
  def handle_unverified_request
    true
  end
Avatar

Thanks for the tip ... I was noticing the same problem ... Every article I displayed on a single page had its own

html
 <html><body>

  </body></html>

wrapper ...

Avatar

Why to_sql function works like this:

Comment.root.to_sql
=> "SELECT comments.* FROM comments WHERE comments.parent_id IS NULL ORDER BY created_at desc"

Comment.includes(:user).to_sql
=> "SELECT comments.* FROM comments"

in the second example real sql query should differs from result shown by to_sql function

Avatar

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).

Avatar

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...

Avatar

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!

Avatar

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! :)

Avatar

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.

Avatar

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.

Avatar

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.

Avatar

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.

Avatar

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

Avatar

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.

Avatar

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.

Avatar

Sorcery has been on my list to check out, but I haven't yet. Thanks for the reminder!

Avatar

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.

Avatar

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!

Avatar

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.

Avatar

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)

Avatar

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

Avatar

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.

Avatar

Looks like @marqueymarc was able to resolve his issue by explicitly adding json_pure and json to the Gemfile.

reference: http://stackoverflow.com/questions/6636929/railscast-geocoding-missing-jsonparsererror-constant

Avatar

@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.

Avatar

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? :)

Avatar

You'd be better doing something like:

ruby
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.

Avatar

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".

Avatar

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!

Avatar

Nothing wrong with hex, but if you want base64, you could do this:

SecureRandom.base64.tr("+/", "-_")
Avatar

What's with Authlogic anyhow? I always felt it was the better level of abstraction compared to Devise.

Avatar

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:

  • 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.

Avatar

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.

Avatar

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...

Avatar

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.

Avatar

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. :)

Avatar

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.

Avatar

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.

Avatar

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!

Avatar

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.

Avatar

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.

Avatar

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.

Avatar

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])