Chapter 6 - Logging in and out
Introduction
In Chapter 4, we implemented a rudimentary default user page, and we promised to restrict access to this page based on the user's login status. This is the first of two chapters fulfilling that promise. In this chapter, we develop a basic login and authentication system, and in Chapter 7 we implement a more advanced system with cookie-based "remember me" functionality.
While it's certainly possible to implement a simple login system using relatively little code, taking the time to build an industrial-strength authentication system is well worth the effort. After all, virtually every web application requires some sort of login machinery for its operation. Moreover, authentication offers a rich variety of problems whose solutions touch virtually every aspect of web programming: forms, database interaction, sessions, cookies, request variables, and more. Of course, the requirements for an authentication system depend on the nature of the application; we hope that the code in these next two chapters can serve as a foundation for whatever sort of login system fits your needs.
Because of the complexity of the RailsSpace authentication system, these two chapters taken together also offer a chance to see the value of keeping our code shiny and beautiful through refactoring, which involves changing the appearance of the code without altering what it does. As part of this effort, we'll write tests for all of our new actions and views, thereby giving us confidence that the essential function of RailsSpace remains unchanged even as we change its form.
Table of Contents
- 6.1 Maintaining state with sessions 131
- 6.1.1 Setting up database sessions 132
- 6.2 Logging in 134
- 6.2.1 Tracking login status 134
- 6.2.2 Registration login 134
- 6.2.3 Debugging with the session variable 135
- 6.2.4 Login view and action 138
- 6.2.5 Testing valid login 142
- 6.2.6 Testing invalid login 145
- 6.3 Logging out 146
- 6.3.1 Testing logout 147
- 6.3.2 Testing navigation 148
- 6.4 Protecting pages 150
- 6.4.1 Protecting pages the stupid way 151
- 6.4.2 Protecting pages the smart way 152
- 6.4.3 Testing protection 155
- 6.5 Friendly URL forwarding 156
- 6.5.1 The request variable 156
- 6.5.2 Friendly login forwarding 158
- 6.5.3 Friendly register forwarding 161
- 6.5.4 Friendly testing 162
- 6.6 Refactoring basic login 164
- 6.6.1 Logged in? 164
- 6.6.2 Log in! 168
- 6.6.3 Log out! 172
- 6.6.4 Clear password! 173
- 6.6.5 Unduplicated form handling 176
- 6.6.6 Unduplicated friendly forwarding 177
- 6.6.7 Sanity check 179
Source Code
- Listing 6.1 app/controllers/user_controller.rb
- Listing 6.2 test/functional/user_controller_test.rb
- Listing 6.3 app/views/layouts/application.rhtml
- Listing 6.4 app/views/layouts/application.rhtml
- Listing 6.5 public/stylesheets/site.css
- Listing 6.6 app/views/layouts/application.rhtml
- Listing 6.7 app/views/user/login.rhtml
- Listing 6.8 app/controllers/user_controller.rb
- Listing 6.9 tests/functional/user_controller_test.rb
- Listing 6.10 tests/functional/user_controller_test.rb
- Listing 6.11 tests/functional/user_controller_test.rb
- Listing 6.12 tests/functional/user_controller_test.rb
- Listing 6.13 app/controllers/user_controller.rb
- Listing 6.14 app/views/layouts/application.rhtml
- Listing 6.15 test/functional/user_controller_test.rb
- Listing 6.16 test/functional/site_controller_test.rb
- Listing 6.17 test/functional/user_controller_test.rb
- Listing 6.18 test/functional/user_controller_test.rb
- Listing 6.19 app/controllers/user_controller.rb
- Listing 6.20 app/controllers/user_controller.rb
- Listing 6.21 app/controllers/user_controller.rb
- Listing 6.22 app/controllers/user_controller.rb
- Listing 6.23 test/functional/user_controller_test.rb
- Listing 6.24 test/functional/user_controller_test.rb
- Listing 6.25 app/views/layouts/application.rhtml
- Listing 6.26 app/controllers/user_controller.rb
- Listing 6.27 app/controllers/user_controller.rb
- Listing 6.28 app/controllers/user_controller.rb
- Listing 6.29 test/functional/user_controller_test.rb
- Listing 6.30 test/functional/user_controller_test.rb
- Listing 6.31 app/helpers/application_helper.rb
- Listing 6.32 app/controllers/user_controller.rb
- Listing 6.33 app/controllers/user_controller.rb
- Listing 6.34 app/controllers/user_controller.rb
- Listing 6.35 app/views/layouts/application.rhtml
- Listing 6.36 app/views/layouts/application.rhtml
- Listing 6.37 test/functional/user_controllor_test.rb
- Listing 6.38 app/controllers/user_controller.rb
- Listing 6.39 app/controllers/user_controller.rb
- Listing 6.40 app/models/user.rb
- Listing 6.41 app/models/user.rb
- Listing 6.42 app/controllers/user_controller.rb
- Listing 6.43 app/models/user.rb
- Listing 6.44 app/controllers/user_controller.rb
- Listing 6.45 app/controllers/user_controller.rb
- Listing 6.46 app/models/user.rb
- Listing 6.47 app/controllers/user_controller.rb
- Listing 6.47.5 (unnumbered) app/controllers/user_controller.rb
- Listing 6.48 test/functional/user_controller_test.rb
- Listing 6.49 app/controllers/user_controller.rb
- Listing 6.50 app/controllers/user_controller.rb
- Listing 6.51 app/controllers/user_controller.rb
- Listing 6.52 app/controllers/user_controller.rb
- Listing 6.53 test/functional/user_controller_test.rb
Errata
As of the first printing, these are the known corrections:
- In Rails 2.0.2 use
<pre><%= session.to_yaml %></pre>instead of<%= debug(session) %>. - Instant Rails Users - Testing flash[:notice]
Instant Rails is a bit smarter than regular Rails when it comes to the flash[:notice] assignment and de-assignment. In Instant Rails, as soon as the flash[:notice] is used, it gets nullified. So, the flash message appears, but then this test:
assert_equal "Invalid screen name/password combination", flash[:notice]fails because the flash is already nil. So, you have to instead check the HTML that was that delivered:
assert_tag :div, :content => "Invalid screen name/password combination", :attributes => { :id => "notice" } - p. 133. Rails 2.0 users might get errors like
ActionController::InvalidAuthenticityToken in User#register Showing user/register.html.erb where line #2 raised: No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store).
This is a known problem in Rails 2. To fix it, comment out the line with the :secret key in app/controllers/application.rb. - pp. 136-139. The screenshots have the Register link right-aligned, but really they should be at the left with the other links.
- p. 153. In the box "A rough edge", two instances of unless should be if:
should beunless session[:user_id] == nil
andif session[:user_id] == nil
should beunless session[:user_id].nil?if session[:user_id].nil? - p.175. There is an unnumbered code listing with code that belongs in
app/controllers/user_controller.rb. It is numbered as listing 6.47.5 above. - p. 179. Depending on your system, you may have "17 tests, 80 assertions".