#270 Authentication in Rails 3.1
Jun 13, 2011 | 7 minutes | Authentication, Rails 3.1
Here I show off three new features in Rails 3.1 that will help with authentication: easier HTTP Basic, SecurePassword in the database, and forcing SSL.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Ryan, is the "force_ssl" needed if nginx setup with force ssl redirection?
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?
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 abefore_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.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".
Remove the dot in the end of the url.
[Ryan Bates: fixed it]
*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.
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?
Are you referring to
flash.now.alert=
? That was working in the episode and is available in older versions as well.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)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?
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.
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!
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.
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.
@lazylester - you probably got an answer already.
The screencast and what Ryan mentioned are indeed authentication because they help you keep trail of all activities for a digital identity. This is what most public websites need. But if you need some link between the physical person and digital id (for example to prevent 1 person from having multiple accounts) you can use an approval flow. Look at authentication gems such as devise. Devise allows you to mandate approval of new registration.
There are other strategies worth looking at too. For example, Google encourages people to link their account(s) to a mobile number. That way even if a person has multiple google accounts, they can be linked together via a common mobile number. You need to decide what works for your application.
I hope that helps.
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.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!
The validates alternative would be.
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.
Eric,
Dynamic finders (
User.find_by_email
etc.) are not deprecated or old school in Rails 3.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:
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.
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.
You can also use
a :password_confirmation accessor will be automatically created and available in the view, though validation will not be triggered if password_confirmation is nil.
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
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).Hello Ryan. Thank you for great tutorials! :)
I didn't quite get this comment though. Isn't it what session hijacking is about?
I may be misunderstanding your comment, so please correct me if my basic understanding about the security design is wrong.
If some malicious people gets user_id, he can create a request with session[:user_id] to by-pass login. So, it is always better to use SSL to avoid session cookies to be sniffed out.
To enhance the security further in highly sensitive system, it would be better to use a session id that changes its value according to the login time (unlike user_id)
My bad. I didn't know that Rails3 session cookies are tamper proof... Wow, that makes my life really easy!
No password salt?
BCrypt stores the salt with the password hash I believe.
Use BCrypt::Password.create() instead of BCrypt::Engine and the library takes care of the salt for you:
http://bcrypt-ruby.rubyforge.org/classes/BCrypt/Password.html#M000007
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.
Actually, bcrypt-ruby uses this method to parse the digest string:
The return of which is [version, cost, salt, hash]. So:
It is definitely cool!
Hi Ryan:
As always, a wonderful screencast.
Is CanCan compatible with the new Rails 3.1 authentication?
Thanks,
-Alex
Yes, CanCan just relies on a
current_user
method. It is compatible with pretty much any type of authentication.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.How can I do the same thing without using devise and instead use the new methods?
Something like this in the ApplicationController.
What about creating a user via the console? I can't find any methods for creating the password_digest.
Just the same as in the controller
The pasword hashing is done internally.
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).
The method you describe is more susceptible to SQL injection, in my opinion. It's better to compare in the code instead of letting the DB do the authenticating. This also benefits from separation of concerns, the DB is responsible for storing and retrieving data not authenticating a user.
I was talking about the interface the developer uses. The implementation under the hood is the same as ryan's.. or similarish. the point is that it is under the hood.
Anybody know how to create a fixture (traditional YAML style) for a model using
has_secure_password
?just use:
I didn't test that, just writing from head but should do the job.
Thanks for that tip. Just to clarify, you need to close this tag in following manner:
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" means NOTHING to a user. How can we suppress this first error when the validator is in Rails 3.1 source?
Actually, you don't need to have a:
line in the User model, as under the hood there is a:
in ActiveModel lib under active_model/secure_password.rb.
Unfortunately, it has no option to customize the message, making it a nightmare for internationalization.
Can't you use Active Record's built in I18n API here?
Take a look at this:
https://github.com/svenfuchs/rails-i18n/blob/master/rails/rails3/active_record.yml
You are right, it can be customized like this:
I did this to delete that error message
module UsersHelper
def remove_sensitive_error_messages_from_user(user)
@user.errors.full_messages.delete_if { |msg| msg =~ /digest/ }
end
end
and when you want to display error message in views, call that method with a given user object.
Thanks!
There are number of ways to fix the thing. But this is the cleanest I found so far.
The problem of I18n approach is that it doesn't highlight password input.
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!
I'd like to know too! Jon, did you figure that out already?
Me too!
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, ...
I second andyl's suggestion
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
Is this buildin solution capable to replace and be a real alternative to authetication gems/plugins like authlogic?
Hi Ryan,
You mentioned it is important to name the field password_digest when you created the model. However then the model/view/controller uses password. Does rails somehow know how to match password to password_digest?
If I use a name pass_digest, would rails match to pass?
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? :)
facebook won't be around in 10 years, i need my own login
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!
... So for using has_secure_password with mongoid, make sure you include ActiveModel::SecurePassword
appears unfiltered in the log files (Rails 3.1.rc4) Is this a security issue ?
Hey Ryan,
Thank you for this screencast! I was wondering.. is there a way to "delay" the Password validations when using has_secure_password?
I'm working on a 3-step signup wizard where the last step is the authentication (email and password), however I can't get past the first step because of validation errors (i.e. 2 errors prohibited this user from being saved: * Password digest can't be blank * Password can't be blank).
Any help would be appreciated. Thanks again for your screencasts!!!
J
What about setting a password for the user then in step 3, having the user set (change) his/her password?
(newbie alert)
I'm having a hard time understanding how the successful "signed up!" message appears as shown in the video, esp when comparing it to the downloadable episode code. There doesn't seem to be anything in a View or Layout to output flash[:notice]
This railscast extends work done in the Authentication from Scratch railscast. You'll find the missing code in there.
thanks, Tim.. That cleared things up.
when I got to the SSL portion of the screencast, everything worked fine in Safari with the same sort of message "Safari can't open page".
however when I switched to Firefox, the error message is quite diff't:
"Secure Connection Failed
An error occurred during a connection to localhost:3000.
SSL received a record that exceeded the maximum permissible length.
Likewise the log output from the server, which on Safari was:
now, in Firefox , looks like:
Is this as expected? I note that both say redirected to the SSL equivalent. But the Safari message looks legit whereas the Firefox one looks like more of an error
Edit @sethvargo - fixed code formatting
Did you have any luck with this? I am running into the same problem.
I'm also having this error, but I think it has something to do with me running my server from port 3000:
http://forum.joomla.org/viewtopic.php?p=2317662
also
http://stackoverflow.com/questions/119336/ssl-error-rx-record-too-long-and-apache-ssl
As usual, excellent screencast Ryan. Have you built tests for authentication with has_secure_password? I'm encountering this error when creating a user in a functional test:
Oh boy. I overlooked a failed migration and this was solved by simply fixing my test database structure.
Can you please tell me how did you resolve this issue. I am using Devise for Authentication and I am getting this error.
I had loaded the entire database through the schema.rb thereby not requiring any migrations to be run. But still I am facing the same issue.
Please help me resolve this issue as I am stuck with this from the past few days and unable to deploy my application on heroku because of this issue.
Hello
I'm having an issue with testing this using Rspec and capybara. I can get and post in Rspec which will set and pass session variables, but when I try and do Capybara request specs I cannot. The end result is that I cannot test any page that has before_filter authorize set on its action.
Thanks,
This is driving me crazy!
Having the exact same issue... did you resolve this?
I just set this up using CanCan for authorization. Works great!
I also added a check in the session controller to make sure the user isn't already logged in when visiting the login path:
How can i restrict use of SecureAuthentication to html requests only? I want use HTTP basic authentication for json requests. Thanks
Ok so I have found that out:
Hi there - how do you specify the authentication to use in your "# use http_basic_authentication" ?
I.e. when inside the JSON case, what is the next step to authorise the request? Currently I'm really struggling getting a JSON API via HTTP basic auth
So what's the downside? You mentioned that you prefer rolling your own authentication from scratch -- is this any less secure/effective/flexible/etc... ? Advantages/disadvantages?
Thanks
Thanks Ryan, great screencast, I am using it in my app.
I have a users model with only an email and password_digest attributes.
One question (I really need help!): Once the user is logged in, how can I require the user to enter their current password in order to change/update their email or password?
Thanks!
After reading up on Rails sessions, I added
to SessionsController#create
"One line of code will protect you from session fixation. The most effective countermeasure is to issue a new session identifier and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well."
http://guides.rubyonrails.org/security.html
Got a site I'm working on and have followed the example above and got the authentication working except for the logout link, with the sessions#destroy not deleting the session and the redirect is going to the login page not the root_url.
Any help with this would be appreciated.
Looked over my code again and spotted an issue with my named route..... what a newbie!!!!!
Thanks for a great tutorial Ryan. I've noticed this method, when using Heroku, means that login details are case sensitive. I think it's because of the way Postgres works - has anyone found a way to adapt the code to allow login details to not be case-sensitive for Heroku?
downcase on create and when you authenticate
How would I make HTTP basic authentication conditional? For example, I have a staging server at work, and I only want to make people authenticate if they access the server from outside the local network.
With this, I am always prompted to authenticate—even if
needs_authentication?
explicitly returns false. Movinghttp_basic_authenticate_with
inside a method doesn't seem to work, either—I get anundefined method
error.I found the answer back in episode #82:
Is it possible to do a post to create a new user via json?
I've added validates_presence_of :email, :on => :create
I'm trying.
POST /users.json HTTP/1.1
Content-Type: application/json
{
"email": "myuser@useremail.com",
"password": "1234",
"password_confirmation": "1234"
}
It picks up the email field but not the others. Is it even possible to do a post like this with the password_digest system?
This is what I cam up with for the time being...
Thanks for the screencast, Ryan! For a small project, this is much better than a full-blown authentication system like Devise.
I'm working on an app where I have a Customer model that should just store basic contact information and may optionally include a login with username and password. Is there any way to use has_secure_password if a password is set yet also allow for a Customer to be created without a password?
something like
validates :password, :presence => false
is not possible: rails issue
I have a similar scenario and I just create a password for every Customer
Sorry new to rails/ruby this is all that's needed...
The User.authenticate method will not exist in Rails 3.1.3. I have posted a question here: http://stackoverflow.com/questions/8585104/method-authenticate-misssing-from-user-model-using-secure-password
The workaround that I used is to call
get_logged_in_user(password)
Anybody know more about this issue?
It seems that the issue with "authenticate" is particular to ruby 1.9.3. I tried with Ruby 1.9.2, and I don't see any issues.
I would like to not have to update the password every time I modify a user. How can I leave the password field blank and just ignore the validation for the password? Does anybody know how?
Could someone post an example of what the routes file for this project would look like? Im an extreme beginner, and so far can't figure out how I would configure it correctly.
-Thanks
Browse Source Code: https://github.com/railscasts/270-authentication-in-rails-3-1
I was watching this and a question came to mind. Is there no need ever to put authentication to the model level, that even people using console will need to be authenticated? From what I know, at production technical person with access to app directory can access all data through console with a little know knowledge of the models. Is that right? If there is a need, is it possible?
ACTIVE MODEL CHANGES RAILS 3.1.1
http://weblog.rubyonrails.org/2011/10/7/ann-rails-3-1-1
Remove hard dependency on bcrypt-ruby to avoid make ActiveModel dependent on a binary library. You must add the gem explicitly to your Gemfile if you want use ActiveModel::SecurePassword:
This might help others that might be staring at an Error page, wondering why that gem wasn't required. @rbates You should add it to the show notes. :)
Quick question,
I have all of this working like a charm. However is not clear how I can call the login and what parameters it takes.
For example I would like the use to be signed in when on successful signup.
How can I do it?
Thanks :)
davodesign84,
Have a look here: http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:signin_upon_signup
Hartl doesn't use has_secure_password. But I think you can use same logic when implementing that feature.
I had a problem with the users_controller as posted in the screencast. I copied it directly into my app, and on running got - undefined method `model_name' for NilClass:Class. I'm not sure if this was an issue with my routes, but rails needed a respond_to to be able to handle the "new" request. ie
Probably only an issue if new to rails!
(Currently running rails 3.1.3)
ok, so it was a problem with my routes file. I've now copied the routes from cast #250 (thanks to Tim Fletcher) and everything works fine - without the respond_to code.
Any idea on how to add a restriction to the number of user sessions with this approach? What I'd like to have is one browser window per user. Opening the app in another browser window would display a warning, signing in from another browser window would also display a warning+a choice to destroy the other session.
Has anyone implemented anything like this?
I am on windows and i am getting the following error when i tried to sign up
"NoMethodError in UsersController#new
undefined method `key?' for nil:NilClass"
I am using rails 3.2.3
While I suspect this has been resolved since your post is nearly 2 weeks old, but make sure to restart your app after adding bcrypt-ruby to your gemfile & bundle installing. Not sure if that was the actual cause of your issue, but it resolved the same error on my end.
What is the way to test beyond login_required?
Trying this in spec/requests does not seem to help:
Results in a redirect to the signin page. Can anybody help with this?
Upon login I get the following error returned:
NoMethodError in SessionsController#create
undefined method `authenticate' for #Class:0xb69d4638
Any suggestions?
How do you write a functional test for the login?
Hello. Newbie here.
I cant get the http://localhost:3000/users view to show properly. I get served a:
Unknown action
The action 'index' could not be found for UsersController
Any hints to as what I am doing wrong?
greatfull for any input.
then you'll probably need to create the file:
No promises that that works, but hopefully it supplies some instant gratification.
You'd be better off looking through some tutorials though if you don't understand the errors you're getting.
http://ruby.railstutorial.org/ruby-on-rails-tutorial-book
^ is overkill for newbies, but I haven't found a more gentle learning source
Thank you very much! Ill do that!
HTTPBasicAuth is still the best way IMO to add quick authentication for things that don't need a full blown login system.