Login & Authentication Generator
A controller/model/view generator for easily adding authentication, users, and logins to your rails app. Login generator has no dependencies.
Login generator may be the quickest and easiest way to do authentication. For simple installation instructions see:
HowToQuicklyDoAuthenticationWithLoginGenerator.
See also: Acts_as_authenticated, another descendent of this codebase. You might like to use that instead. Works with Rails 1.2.
SaltedHashLoginGenerator, a newer descendent of this codebase that adds ActionMailer support for changed and forgotten passwords, as well as account verification via a registration email with a custom URL sent to the user’s registered address.
OpenID and OpenidLoginGenerator, a port of LoginGenerator that uses OpenID for authentication.
It’s an extension to the base rails install which you need to download and install with gem using:
gem install login_generator
Once installed, consult the usage page via
script/generate login
and the README_LOGIN for instructions on how to set up your database and use the produced code in your application.
You can find further reading in:
before_filter :login_required, :only => [:secret, :finances]
or the opposite
before_filter :login_required, :except => [:login, :signup]
def authorize?(user)
user.role?('admin')
end
def signup
usercount = User.count
if @session[:user].nil? && usercount != 0
redirect_to :action => "login"
else
...standard code of signup...
@request.session[:user] = true
But wait! That is sort of a hack, and if \LoginSystem#login_required gets any more strict OR you modify \LoginSystem#authorize? your tests will fail.
Here is a better way (add to setup() ) :
@request.session[:user] = Test::Unit::MockObject( User ).new
# then you can start constructing a mock "logged in user"
# for cases like when your other models need to save a user id
@request.session[:user].setReturnValues( :id => 44 )
Note that Test::Unit::\MockObject is not built into the Test::Unit package; you have to download and install it from [http://raa.ruby-lang.org/project/test-unit-mock] and put this at the top of your test (or test_helper if you want) :
require 'test/unit/mock'
NOTE: a previous version of the wiki suggested this:
@request.session[:user] = @bob
…as in the magical bob from account_controller_test.rb, but that wasn’t working for me. [ -kumar303]
I am a newie, so follow my advice at your own peril… but; I just added this to setup():
@request.session[:user] = User.find(1000001)
And it’s been working great for me. The 1000001 user is the “bob” user defined by default in the fixtures. [ -DanMills]
A better solution is to remember to load your fixtures into your functional controller tests, just like in your unit tests. If you look in the generator created account_controller_test.rb you’ll see that this is exactly what it does, and this of course is how @bob magically appears.
So if you add this:
fixtures :users
…to the top of the controller test case you wish to add authentication to, then:
@request.session[:user] = @bob
…should work fine, either in setup() if you want to have an authenticated user present for all tests, or in individual tests as required.
Using User.find(1000001) to obtain a user could be unpredictable as unless you load the fixtures into your test case, this will only work if a previous test run (of another test case that does load the fixtures) has left this value stored in your test database. [ -sma]
Q: I am trying to use the Login in my application, but I can’t find a change password feature. There is a method change_password in the model, but I can’t see any controller or view. How could one put a change password feature to work?
A: There is a change_password method in the model. Be careful to expose it though, changing passwords using this way means that no validation will take place.
Here is another contribution:
Ok, finally I got it to work, but I could not use the helper for input fields. Here is the view:
<%= start_form_tag :controller => "login", :action => "change_password" %>
<div title="password alteration" id="password alteration" class="form">
<div class="error_message"><% if @message %><%= @message %><% end %><br/>
<label for="user_old_pass">Old password:</label><br/>
<input name="old_password" id="old_password" tabindex="2" type="password" /><br/>
<label for="user_new_pass">New password:</label><br/>
<input name="new_password" id="new_password" tabindex="2" type="password" /><br/>
<label for="user_new_pass_confirmation">Confirm password:</label><br/>
<input name="new_password_confirmation" id="new_password_confirmation" tabindex="2" type="password" /><br/>
<input type="submit" value="Alter password" class="primary" />
</div>
<%= end_form_tag %>
and this is the controller:
def change_password
@user = @session['user']
@session['message'] = nil
case @request.method
when :post
unless @user.password_check?(@params['old_password'])
@session['message'] = 'You have introduced a wrong password!'
else
unless @params['new_password'] == @params['new_password_confirmation']
@session['message'] = 'Your password and password confirmation dont match!'
else
@session['message'] = 'Your password was changed successfully!' if @user.change_password(@params['new_password'])
end
end
redirect_back_or_default :controller => "login", :action => "change_password"
end
end
In the model user I defined the method:
def password_check?(pass)
self.password == self.class.sha1(pass)
end
Q: How can I use the method “render_errors” defined in the module \LoginHelper outside the Login controller?
A: Its was a helper method in account_helper.rb. I since replaced it with a similar built-in Rails method.
Q: Can we expand a bit on the authentication parts? I’m trying to develop a personal website where I’m the only person that can edit, add or delete items, but anybody can view items. So I’ve got it set up so that you have to be logged in to add or edit anything, but not to view. This is a terrible security model because anybody can guess the URLs, register, and then sign in, and then they have full access to deface my website.
A: This is where the topics of “logins” and “access control” can be confusing to many people. These are quite different issues.
Consider the complexity involved in a Windows or Unix file system, where each file has “permissions” indicating who can do what to it. Clearly, the logic involved in managing and checking ownership permissions, editing rights, or making something “public” or “private” will probably need to go all through your program’s code.
This topic is usually called “Access Control.” Google for “Access Control Lists” or buy a book about it; be warned: since it touches on security issues, it can be complex. Tobias wrote, “I usually use a token based system where I grant users tokens like "maintainer” in the controllers I want to protect. I overwrite the authorize?(user) method as per example in the lib/login_system.rb."
You can also look at the AccessControlListExample entry on this wiki to get you started.
Q: I have two initial problems when trying to use this generator.
undefined method `redirect_back_or_default' for #<LoginController:0x2f6ce78>#<ActionController::SessionRestoreError: Session contained objects where the class definition wasn't available. Remember to require classes for all objects kept in the session. The session has been deleted.>A: You didn’t change your app/application.rb as advertised in the readme.
When installing login_generator 1.0.1 I get the following errors:
- gem install —source http://dist.leetsoft.com login_generator
Attempting local installation of ‘login_generator’
Local gem file not found: login_generator*.gem
Attempting remote installation of ‘login_generator’
Successfully installed login_generator, version 1.0.1
Installing RDoc documentation for login_generator-1.0.1…
WARNING: Generating RDoc on .gem that may not have RDoc.
templates/controller.rb:8:29: Expected class name or ‘<<’. Got RubyToken?::TkLT: “<”
templates/controller_test.rb:7:9: Expected class name or ‘<<’. Got RubyToken?::TkLT: “<”
Successfully installed login_generator, version 1.0.1
The errors are problems generating the documentation, nothing more.
— LeeO
Q: Thank you, very helpful! The README says I can “get the user object from the session” like such:
Welcome <%= @session‘user’.name %>
\NotAnAnswer: More details about the error are required if you want help tracking it down. Dig through your log. The folks on IRC are also quite helpful.
A: The sample ‘user’ table doesn’t have a name column. Try
<%= @session‘user’.login %>instead, or add the name column to the user table.
RE: A: The previous looks for a method, try
<%= @session[:user].login %>instead.
Q: The README_LOGIN file says I need to call store_location in order to have the user brought back to the action they were on before they logged in. Why doesn’t before_filter :login_required do this automatically?
A: Orestis says: It actually does it.
Q: Maybe a stupid question, but how can I “connect” my user to for example news post? I got it to show it if I manually edit the database (with belongs_to), but how do I make it store the user_id when I create new/edit?
Comment: Don’t forget to tell us the basic commands. If you are having trouble installing it into your app the basic usage is:
ruby script/generate login
For example:
ruby script/generate login user
This isn’t actually that obvious.
Q: I’ve tried reinstalling a couple times, but I keep getting the same error: NoMethodError in Account#signup
Showing /account/signup.rhtml where line #8 raised: undefined method `login' for #<User:0x409f9218>
I guess I can get around this by changing the login form to use text_field_tag instead of text_field… but shouldn’t there be a better way around this (or a fix?). And when I fix the login form, it throws yet another error, telling me that
<a href="http://wiki.rubyonrails.org/rails/pages/NoMethodError" class="existingWikiWord">NoMethodError</a> in Account#signup undefined method `login=' for #<User:0x409f81ec> app/controllers/account_controller.rb:20:in `new' app/controllers/account_controller.rb:20:in `signup'
anything? ideas? thanks.
A: Try changing the table so that column names are lower-case.
After pondering and researching and pondering and researching on a whim I tried capitalizing “login” and “password” in signup.rhtml. What do you know, it worked! It may be due to the column names in the table being capitalized.
After attempting to “sign up” I found that other “password” methods were “missing”. My problem was actually that Rails expects the columns to be lower-case.
A: Are you using SQLite? I found that the README file contains sample database creation scripts for each database, but the one for SQLite uses a ‘user’ column which I think should be ‘login’ I changed my column name and this resolved the undefined method problem.
Q: I’ve installed login_generator on FC3 and it works great.
That said when I install it on XP SP2, I get an error when I modify the app controller to require_dependency:
<span class="newWikiWord">MissingSourceFile<a href="http://wiki.rubyonrails.org/rails/pages/MissingSourceFile">?</a></span> (No such file to load -- login_system.rb):
c:/ruby182/lib/ruby/gems/1.8/gems/activesupport-1.1.1/lib/active_support/dep
endencies.rb:193:in `load'
The files appears to exist:
/cygdrive/c/ruby182/lib/ruby/gems/1.8/gems/login_generator-1.1.0/templates
$ dir
README helper.rb user_test.rb view_logout.rhtml
controller.rb login_system.rb users.yml view_signup.rhtml
controller_test.rb user.rb view_login.rhtml view_welcome.rhtml
I did gem install rails and gem install login_generator and both went through without an error. Then I uninstalled ruby, rails, gem and everything else I could find and tried again, same result. Any help appreciated.
Not an Answer: Running the login generator should put a copy of login_system in your applications /lib directory — That’s where it’s being looked for, not the gems directory.
(Back to Q:)
Thank you, that is helpful.
So
C:\src\deploy>rails testing
create
create app/apis
<snip>
create lib
<snip>
C:\src\deploy\testing> gem install login_generator
Attempting local installation of 'login_generator'
Local gem file not found: login_generator*.gem
Attempting remote installation of 'login_generator'
Successfully installed login_generator-1.1.0
C:\src\deploy\testing\lib>dir
Volume in drive C has no label.
Volume Serial Number is 1087-60CC
Directory of C:\src\deploy\testing\lib
08/13/2005 10:28 PM <DIR> .
08/13/2005 10:28 PM <DIR> ..
0 File(s) 0 bytes
2 Dir(s)
(so, login_system is not created in my app anywhere)
then create a dummy controller, and modify the app.rb controller to require the login_system, then load the controller
No such file to load -- login_system.rb
Like I said this worked flawlessly in linux, not clear to me what I am doing wrong but thanks for the help, the above is as tight as I can boil it down to.
C:\src\deploy\testing\lib>gem --version 0.8.10 C:\src\deploy\testing\lib>ruby --version ruby 1.8.2 (2004-12-25) [i386-mswin32]
(Back to) Not an Answer: Gem is a tool for installing Ruby libraries on your system. Once login_generator is available, you must then use the generate command to “instanciate” or “generate” the code for an individual Rails application.
See the usage page for instructions on generating your login system for the app in C:\src\deploy\testing
script/generate login
Note: If you get errors throughout your site as I did on Windows, you’ll have to replace require_dependency with require_library_or_gem for “application.rb”.
Important: The
crypt_unless_empty should be modified. According to Jeremy Hubert it should be def crypt_unless_empty user = self.class.find(self.id) write_attribute “password�?, self.class.sha1(password) if !password.empty? && user.password != self.password end
Also, I got an “invalid char” error from copying the code above. Just replace the " " with regular quotes if you get the same error.
Q: Is there a good how-to for integrating cookies into this login system to keep people logged in across sessions? Thanks!
A: ???
1 From login generator’s Changelog
Q: I try adding the code for change password from up top of this page. But when I try to change password the following error show up
NoMethodError in
Account#change_password
You have a nil object when you didn’t expect it!
The error occured while evaluating nil.password_check?
RAILS_ROOT: ./script/../config/..
Any clues what I did wrong?
Not an Answer: I’m having the same problem and it seems the change_password method that the question and answer refer to is not actually in the model. The solution posts a change_password method to add to the login_controller, but not to the model. The controller’s change_password actually calls the change_password that is supposed to be in the model. I guess I’ll have to try to write one. See the following answer for my solution and post if it worked.
A: I did end up writing one. After experimenting, testing, and failing time and again, I was eventually led to an aggravatingly simple solution. In the User model, define a change_password method as such:
def change_password(pass)
self.password = pass
self.save
end
and add this validation:
validates_presence_of :login, :password, :on => :update
self refers to the object that called the method, in this case @user from the controller. It changes the user’s password field to the value of pass(but it is not yet encrypted, that comes next). Calling save will in turn call crypt_unless_empty via the before_update :crypt_unless_empty filter. It is here that the password will be encrypted via write_attribute. Since the change_password page already checks for the password_confirmation we only need to validate password when we update, otherwise the validation of password_confirmation fails.
category: Generator
Q: I’m creating a class attendance system for a class and my user accounts are being used as teachers. I added a list action/view for users so teachers can be associated with a class by clicking an action link but I’m getting a nil object error on the list with the following code:
10: <% for user in @users %> 11: <tr> 12: <td><%=h user.firstname << " " << user.lastname %></td> 13: </tr>
The problem arises on line #10.
A: Is @users being defined in the controller that calls this view? You should have
@users = User.find(:all)
somewhere in the controller or view. Also, you may be able to use the User method fullname() to render the full name instead of building it manually:
12: <td><%=h user.fullname %></td>
Q: How do I add a “Logout” link to “standard-layout.rhtml”?
A: Well, you can just add a regular link to your controller and logout action, like www.thegregg.com /user/logout
Or you can call it in ruby through the html.
<%= link_to ‘Logout’, :controller => ‘user’, :action => ‘logout’ %>
Hope that helps.
- Gregg Hanson
www.TheGregg.com
A: add link_to,controller = account,action = logout
A: define some timeout value, eg. 1hour. If user has logged in, set the time. When user performs any action, check the time, if is still within timeout frame – if not, user is forced to login again, else set new time. And if you want to count number of users logged in, simply count users with ‘valid’ time (they’ll be within timeout time frame)
Q: I am trying to make an edit member utility, but it requires the password change. If you leave the password blank it says you need one, but if you take out the password part of the form it says the confirmation is too short. Is there a way to have them edit their member info without needing the password confirmation?
A:
Edit views/users/_form.rhtml
<%= password_field 'user', 'password_confirmation' %>
Update models/user.rb
attr_accessor :skip_password protected def password_required? @skip_password != true end validates_confirmation_of :password, :if => :password_required? validates_length_of :password, :within => 5..40, :if => :password_required? validates_presence_of :password, :password_confirmation, :if => :password_required?
Update controllers/users_controller.rb
def update
...
if @params[:user][:password].empty?
@user.skip_password = true
end
if @user.update_attributes(params[:user])
...
Q: i’ve installed the login generator , created the controller e.t.c without a hitch, but when i try to run it, it says:
NoMethodError
undefined method `model’ for ApplicationController:Class.
dunno why, but i fink its in relation to the
“include LoginSystem
model :user”
statement in my application controller class.
pls help!
plus: DO I HAVE TO RUN IT WITH THE FOOBAR… URL or can i just use mine. cheers
Q: Same problem for me, though I’ll explain mine so you can understand it.
Upon going to localhost:3000/account/signup (as in the example), I get a NoMethodError as follows:
undefined method `model' for ApplicationController:Class
It refers to Line 12 of applicaton.rb, which is:
model :user
Q: Same problem here. I got all the kinks out, but at the expense of authentication no longer functioning. That is, any username and password gets you to uid 1’s page.
A: I suspect you’re running Rails 2. This gem is written for Rails 1. The ‘model’ method is no longer supported. Kill that line from the code.
If this is indeed your problem, you’ll run into a few other problems. The ‘method’ method barfs because ‘method’ is a keyword. You can substitute the ‘.method’ call with ‘.request_method’ with few side effects. (.method is just a wrapper to treat HEAD requests as GET requests). Also, several common objects are no longer preceded by the @ symbol (@request, @session, @params).
Hopefully that will help get you started in the proper direction. -d