RailsCasts Pro episodes are now free!

Learn more or hide this

Recent Comments

Avatar

Very nice solution, but is there a specific reason to not use Boolean tables within the user model for each role ? (user.amin, user.moderator etc.) It seems to be easier this way to delete roles and all the corresponding data through migrations.

Avatar

To get the ball rolling - here are some example gists that show how to do 'remember me' and 'redirect to target page'.

'remember me' sets a cookie. if the person kills the browser, then opens a new session and returns to the page, the user will be auto logged in. cookie times out in 100 hours.

'redirect to target page' sets the referring URL in the protected controller (using authenticate_member!). then after the person logs in, they are redirected to the page they wanted.

Login Form: https://gist.github.com/1032488
Sessions Controller: https://gist.github.com/1032490
Application Controller: https://gist.github.com/1032492

Avatar

Using this screencast, I was able to get rid of Devise in about 20 minutes. THANKS!!

I think a lot of people would benefit from a screencast that shows 'advanced' authentication options like: remember me, forgot password, session timeouts, email registrations confirmations, ...

Avatar

One thing you missed in this Tutorial is .
Adding :username in accessible mode in user.rb (user model)
other

Avatar

Same problem here. On local server is working, but when pushing to heroku I have this issue.

Avatar

I use this functionality on many controllers, and extracted common controller actions as a module: https://gist.github.com/1030815

Avatar

Is it possible to have a nested model solution, for example, where each painting requires that you upload three images instead of just one, and then stores them in an Images table with a painting_id column and gallery_id column, instead of just an images column in the Paintings table?
Im trying to do this and having trouble getting the logic down.

Avatar

@Alexey Poimtsev

Because you're missing the :validatable module from Devise which adds the password_required? method.

Avatar

Anybody know a way to "un" force_ssl? I've got it set on one controller, but if a user clicks a relative link on my page, I want to revert to http. I can think of a few ways, but they're all pretty nasty. Hoping there's a simpler way.

Thanks in advance for any help!

Avatar

I think you are confusing authentication and authorisation.

Authentication is only to make sure you are who you claim to be logging on.

You are talking about authorization which defines if you are allowed to access determined parts of an application/data.

Avatar

I like this but can't figure out how to override the duplicate validation errors when say the password field is left blank:

  • Password digest can't be blank
  • Password can't be blank

"Password digest" means NOTHING to a user. How can we suppress this first error when the validator is in Rails 3.1 source?

Avatar

I found out the

ruby
:require => false if RUBY_PLATFORM =~ /darwin/i

get some trouble on heroku. It tries to install it even if it specified on group :test, :development. I commented it and it works fine

Avatar

Anybody know how to create a fixture (traditional YAML style) for a model using has_secure_password?

Avatar

Hi ! Really nice tutorial. Everything work without AJAX :( Its mean keyup :( Idon`t now why.

Avatar

Be very careful with this. The query you use will fetch all the users and puts LOWER to it. If you have 100.000 users, it will fetch them all and it wont use indexes.

It is better you put after create filters to lower case the email address.

Avatar

Personally, I find using a 'login' method that takes a username (or email if you like) and a password is shorter and nicer than 2 lines of first finding the user in the db and then checking for the password explicitly.

See my gem https://github.com/NoamB/sorcery for an example (wiki).

Avatar

lazylester, you are getting philosophical here. In this screencast there is no way to ensure that you are who you say you are at signup, but the achievement here is to authenticate you every other time afterwards, since there is a password attached to your user model.

Now, we could wait for confirmation (like in devise) from the given email address, but... who ensures us that the given email address really represents the person who you say you are? Since there are so many ways to cheat that confirmation procedure, the extra check doesn't give us a 100% security. Therefore, even though this is a worthy discussion, I find your comment a little out of place.

Avatar

the way BCrypt is, is really cool. Here is a password_digest: $2a$10$QIFk4ytMIzE03/njtSMFmedzhTyv8DVMMtWjqnFeW9FcQpBEf.u0.

I believe 2a or $2a is the salt, $10 means 10 encryptions/stretches and the rest after the next $ (or included) is the resultant hash.

Avatar

I don't like them since they take longer, sure it's by uncountable times, but it's really not too much for me to add the extra code. Also I do this:

class User # ...
  def find_by_email(email)
    where(['LOWER(email) = LOWER(?)', email]).first
  end
end
Avatar

a lot of people want people to register to see content, that way they can send mass e-mails, say that have x users, etc. If you want to have manual activation it's not that hard, just add an extra field such as add_column :users, :activated, :boolean, :default => false, :null => false and then have an admin panel and set user.activated = true on your users.

Avatar

I remember that I've done something similar before but used flash.now.success and got an error that success was undefined, is it only selected names or was it something else I did wrong? (It was so long ago and it's a pain to create a controller to test them XD)

Avatar

*has_secure_password ;P
Yeah, I don't know why they aren't accepting pull requests that allow you to rename what column it goes into.

Avatar

In Mongoid 2.0 (and I believe 2.0.1 as well) you need to include the following in your model if you're going to use Date or DateTime

include Mongoid::MultiParameterAttributes

Source: https://github.com/mongoid/mongoid/issues/30#issuecomment-1211911

Avatar

What do I mean? Well the screencast is titled "Authentication in Rails 3.1", and yes, authentication means proving you are who you claim to be. But the sign-up procedure doesn't do that. (Does it? How?)

You say, Sean, that authentication means that you can modify your own resources and not other peoples. That's authorization, not authentication.

I'm not seeing how sign-up supports authentication. But I can be a bit dense sometimes! So be gentle!

Avatar

Just the same as in the controller

ruby
User.create(:email => 'my@mail.org', :password => 'mys3cret')

The pasword hashing is done internally.

Avatar

What about creating a user via the console? I can't find any methods for creating the password_digest.

Avatar

Something like this in the ApplicationController.

ruby
def authenticate_user!
  if current_user.nil?
    redirect_to login_url, :alert => "You must first log in to access this page"
  end
end
Avatar

Yes, CanCan just relies on a current_user method. It is compatible with pretty much any type of authentication.

Avatar

Hi,

I have previously used devise for authentication but think it is to heavy with too many functions and too much I have to customize. This new built-in methods are really nice.

In devise you use a before_filter to authenticate certain actions, e.g.

some_controller
before_filter :authenticate_user!

How can I do the same thing without using devise and instead use the new methods?

Avatar

BCrypt stores the salt with the password hash I believe.

Avatar

Eric,

Dynamic finders (User.find_by_email etc.) are not deprecated or old school in Rails 3.

Avatar

Hi Ryan:

As always, a wonderful screencast.
Is CanCan compatible with the new Rails 3.1 authentication?

Thanks,
-Alex

Avatar

The validates alternative would be.

ruby
validates :password, :presence => {:on => :create}

Both alternatives are longer so I don't know if they are necessarily better. The other way may be deprecated in 3.1, I haven't checked yet.

Avatar

Yes, it is safe to store the user_id in a session. If you're using cookie based sessions then the client can read the data inside, but they can't write to it. Basically you don't have to worry about someone changing the session, but you do have to be careful to not store sensitive information in there (such as the user's passwords).

Avatar

Are you referring to flash.now.alert=? That was working in the episode and is available in older versions as well.

Avatar

On the surface they do basically the same thing: redirect to https, but at different levels. Apache/Nginx will redirect before it ever hits the Rails app, so it will be quicker and it won't go through the Rack middleware stack at all.

The force_ssl call in the controller happens in a before_filter so it will happen later. The main benefit of this is that you can add/remove it for certain actions and use any Ruby logic you want. If you want every page in your app to use SSL then doing it at the web server level is probably best.

There is also a config.force_ssl = true option you can place in your app config which will use middleware and redirect every page. That is closer to Apache/Nginx solution but is nice if you don't have access to that config such as on Heroku.

Avatar

Hello! This is certainly not the first time I am implementing authentication, but the question rises again, as I need it in a social website.. is using session[:user_id] safe by default?

arguments:
http://stackoverflow.com/questions/623379/rails-tracking-a-users-id#answer-623463
http://www.issociate.de/board/post/475749/Is_it_safe_to_store_user_id_in_Session?.html

Avatar

Great screencast. I have a couple of questions though after watching it. I noticed that you are using a couple of 'old school' methods in your code which I thought were not considered the 'Rails 3' way of doing things. I'm referring to:

user = User.find_by_email(params[:email])

and

validates_presence_of :password, :on => :create

Is this still considered good practice in Rails 3 when we have the alternatives?

user = User.where(:email => params[:email]).first

and

validates :password, :on => :create

Thanks for your help!

Avatar

What do you mean? Thousands of sites/apps allow user sign up, but still require authentication to make sure a given user is who she claims to be. Authentication just means that you can add/change/delete resources, settings, etc. under your own account and not anyone else's. This screencast focussed only on the authentication side of things, but the developer is responsible for authorization, which is highly dependent on the kind of app being developed. Usually authentication is a bit more generic.

Avatar

Same question for apache, I have apache/passenger forcing a rewrite on all pages to the https equivalent. would the force_ssl replace the need for that rewrite?

Avatar

I'm probably missing something obvious here. But what is the use case for the "sign up" authentication method?

If anyone can sign up and set up a password, isn't the site just as unprotected as a site with no password?

Avatar

Remove the dot in the end of the url.

[Ryan Bates: fixed it]

Avatar

I noticed you used "flash.now.warning" = instead of "flash.now[:warning] =". I was curious about it but it doesn't work in 3.1RC4. Was this just a mistake or is there a plugin that has this functionality?

Avatar

Is there any way to get image dimensions in my view?

@user.avatar_width(:thumb) or @user.avatar_height(:thumb)

Something like this?

Avatar

If you need something like hash_secure_password on Rails 3.0.x, you can use my plugin at https://github.com/SMWEB/salt-and-pepper . As a bonus, it only uses a single column in the database and doesn't force you to call your attribute "password".

Avatar

Ryan, is the "force_ssl" needed if nginx setup with force ssl redirection?

Avatar

brilliant thanks demetrios, I'm not fluent enough with textmate to know where to start looking for these things

Avatar

for some reason I had to had angle brackets around my prePopulate setting like so:

prePopulate: [$('#book_author_tokens').data('pre')]

not quite sure why, could be because my fields are single value.