A controller/model/view generator for easily adding authentication, users, and logins to your rails app.
See also: Acts_as_authenticated, another descendent of this codebases. You might (no, you want) 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.
WikiGardening is needed here. The most recent version of this page has been lost. Currently this page is poorly structured and doesn¡¯t provide enough information on how to install/use this generator.
For quickly getting started, see HowToQuicklyDoAuthenticationWithLoginGenerator.
It¡¯s an extension to the base rails install which you need to download and install with gem using:
gem install login_generatorOnce installed, consult the usage page via
script/generate loginand the README_LOGIN for instructions on how to set up your database and use the produced code in your application.
Documentation
You can find further reading in:
- Authentication overview
- the AccessControlListExample
- the README. After you run the generator you will find a README_LOGIN in your rails application folder.
- the ActiveRecord documentation that explains validation ActiveRecord::Validations::ClassMethods
- make sure you have a quick look at Gotcha to make sure you know the traps you¡¯re likely to fall into.
How do I ¡
- ... protect only certain actions?
before_filter :login_required, :only => [:secret, :finances]or the opposite
before_filter :login_required, :except => [:login, :signup]- ... restrict access to certain users? There are many approaches for user security. This ranges from name based lookup over token systems to full fledged ACLs (AccessControlListExample) or even ACL trees. Your security system should be unrelated to the login system and should live in your models. In my applications i usually use a token based system and add a method to my user models to query if a token is available. You can then test the logged in user by implementing your own authorize?(user) method. Here is an example:
def authorize?(user)
user.role?('admin')
end
- ... restrict access to creating new accounts? Here¡¯s a modification to the standard ¡°signup¡± function that will redirect /signup to /login, unless there are no accounts created yet:
def signup
usercount = User.count
if @session[:user].nil? && usercount != 0
redirect_to :action => "login"
else
...standard code of signup...- ... test my controllers that require login?
If you want to simply fake the tests into thinking the user is logged in, this seems to do the trick :
@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]
Questions
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.- I get a the following error after successful signup:
undefined method `redirect_back_or_default' for #<LoginController:0x2f6ce78> - When I visit my previously working pages I get a ¡°Not Found¡± error and this shows up in my log:
#<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 <a href="http://dist.leetsoft.com">http://dist.leetsoft.com</a> 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 <span class="newWikiWord">RubyToken<a href="http://wiki.rubyonrails.org/rails/pages/RubyToken">?</a></span>::TkLT: "<"
templates/controller_test.rb:7:9: Expected class name or '<<'. Got <span class="newWikiWord">RubyToken<a href="http://wiki.rubyonrails.org/rails/pages/RubyToken">?</a></span>::TkLT: "<"
¡ªTomas Pospisek
Note the line:
Successfully installed login_generator, version 1.0.1The 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 %>
...yet this gives me an Application Error when I put it in standard-layout.rhtml. What am I missing?
\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.
- looks like @session[:user].login works better (note :user instead of the example ¡®user¡¯)
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 <insert name here>
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 loginNote: 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_emptyshould 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
Otherwise, if using a regular update on the user object, the password could be re-encrypted and hence user won¡¯t be able to log on.
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: ???
fn1. 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
endand add this validation:
validates_presence_of :login, :password, :on => :updateself 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.
discuss this topic to forum
