Screencast: Using Clearance for authentication, Part 2 of 2

November 2nd, 2009 Leave a comment Go to comments

In Part 1, we showed creating an application with authentication supplied by the Clearance gem.

In Part 2, I’ll show how to retro-fit an application with Clearance. This is trickier, because we need to think about legacy data. We also need to remove code for our old authentication system.

The screencast link is at the bottom of the page; I’d recommend you read or skim this page first before watching.

Two notes:

NOTE 1: We’re providing a download of the completed app, with a sample development database (since, as I note below, the inclusion of Clearance means that the database can no longer easily be migrated completely).

NOTE 2: Changing the functionality of an app component is always tricky, and it may well be that I’ve missed something in a view or controller; let me know if anything seems amiss.

Here are the steps:

  1. Make a new project with all of the code from the ccc2 example.
  2. Make sure that this application is completely migrated (if it isn’t already). Once we add Clearance, it will be hard to run the migrations from the very start, because the Clearance validations are incompatible with the User model we started with.
  3. Edit the config/ enviroment files as in the first screencast, namely:

    In config/environment.rb:

    config.gem "thoughtbot-clearance",
      :lib     => 'clearance',
      :source  => 'http://gems.github.com',
      :version => '0.8.2'
    
    # Outside of the config block . . .
    DO_NOT_REPLY = "donotreply@example.com"
    

    In config/environments/development.rb and config/environments/test.rb:

    HOST = 'localhost:3000'
    
  4. Install and “vendor” clearance, generate the code, and migrate:

    rake gems:install
    rake gems:unpack GEM=thoughtbot-clearance
    script/generate clearance
    rake db:migrate
    

    NOTE: Clearance is smart! If Clearance finds an already-existing users table, it will add the columns, not create the table. After generating, check out the migration.

  5. Now we need to remove our old authentication cruft:
    1. From application_controller.rb, remove #logged_in? and its helper_method declaration.
    2. in requires_authentication_controller.rb: remove everything except before_filter :authenticate
    3. Remove session_controller.rb, users_controller.rb, and the route for users in config/routes.rb; remove app/views/sessions, app/views/requires_authentication, app/views/users
    4. from application_helper.rb, remove #logged_in?
  6. Try out the application, and get a feel for what’s working and not working.
  7. Fix Up the views

    Everywhere, replace logged_in? with signed_in?

    Wherever we have :controller => :session, :action => :logout or :action => :login, we need to replace with sign_up_path, sign_out_path

    Finally, Clearance sets a Flash under the key :failure. Let’s update our Flash display in application.html.erb to display Flashes no matter what key they’re under. Find the Flash HTML, and replace with

    <div id="flash">
      <% flash.each do |key, value| -%>
        <span class="important"><%= html_escape(value) %></span>
      <% end -%>
    </div>
    
  8. This would be a good time to try out the application. Everything working pretty well? But . . .
  9. Note that when we are signing in, the links to our own controllers are wrong. This is because we are in a Clearance controller, which is itself in a module: So the prefix for the Clearance module is in play. One way to fix this is to find where we say :controller => :invitations, and replace with :controller => ‘/invitations’ (using an absolute path). Those links are in the layout and in the welcome page.
  10. Lets try and add a field to the “new user” form. For example, now that we have a more sophisticated authentication system, let’s capture the first_name when a user registers.

    First, add first_name to new user form in vendor/gems/thoughbot-clearance-0.8.2/app/views/users/_form.html.erb

    If you like, see how this works. You should find that the value supplied for first_name is not getting saved to the database.

    To fix: in app/models/user.rb (that is, our own user.rb; not the one in the Clearance gem):

    attr_accessible :first_name
    

    Here’s why we need to do that: http://wiki.github.com/thoughtbot/clearance/attr_accessible

    I have to say, I’m not especially pleased that I have to hack the form deep inside the Clearance gem, but that seems to be the way to do it.

  11. Providing password for “old” users (who didn’t have them).

    Arguably, you could write a migration to change the password for all users who don’t have one to something they might know: Such as making the password the same as the e-mail address. This would actually be hard, because Clearance isn’t set up to make this easy (just for example, the method on the Clearance User extension will only calculate a “salt” value for new records).

    Instead, what you should do is find all of the users in the database with no password, and e-mail then the “Change your password” link (http://localhost:3000/passwords/new).

Click for screencast

  1. qiangwu
    November 29th, 2009 at 20:07 | #1

    Hi, there

    I follow steps and doing good on standalong case like "Note" in screencast 1 shows.

    But I am getting trouble to create a valid User using "Sign_up" link. I study the User controller and Session Controller (they are hide deep down in Vendor, anyway I can create my own customized session and user controllers in my regular app dir ?), I traced the link step by step, only to find that Once the link "Sign_up" pressed, the empty ::User.new already be created by the params[:user], which is nil, but I didn't input any thing in those three fields yet.

    Once I fill in the email/pwd/confirm field and press "Sign_Up", it goes nowhere. Actually back to "session/new", which is sign_in form (two field). I check console finding nothing created. Did I miss a step ?

    For the settings, I "include Clearance::Authentication" in the application_controller and "before_filter :authenticate" in the other working controller (page_controller in my proj). Are these in right position ?

    Those screencasts are very helpful in helping us to set up the framework, I guess I just caught up with those tiny details.

    I am doing my final proj these couple of days!
    How 's your holiday!

  2. November 29th, 2009 at 20:17 | #2

    @qiangwu

    Yes, "include Clearance::Authentication" goes in the ApplicationController; and " before_filter :authenticate" goes at the top of any controlle ryou want to protect.

    Double-check your log. You should see the sign-up link as though it is about to be mailed out.

  3. qiangwu
    November 29th, 2009 at 20:25 | #3

    Hi, John
    How 's your Thanksgiving

    Thanks for quick response.

    Unlike restful_authentication [ http://www.AgileWebDevelopment.com ],which has many full-size examples on the web, clearance provides a better UI, but not much source (examples) on google sites.

    I believe I can catch the bug by checking the log.

    Thanks again.

  4. qiangwu
    November 30th, 2009 at 00:09 | #4

    Hi, John

    I pass it. By tracing the log files side by side (I started two servers, one for scratch, one for my porj), I finally spot the bugs.

    I created a navigation head bar let user to choose pages, and I placed the button 'Login'/'Logout' at the very end of that bar.

    It turns out that Button which I assigned it to "sign_in_path"/"sign_out_path" doesn't quite follow that sign_path, but following its neighbor's path, which is the view_page_path, insead of signing out of current session, it starts doing its neighbor's job by complaining "can't ... without ID". That's why I can't get logout all the time.

    Once I hard coded those navigation page button with "control=>action" sequences, the Login/Logout button is forced to obey its own path, which is sign_in_path/sign_out_path.

    This bug costs me over 10 hours. The rails' concept of convention over configuration for me is to get used to a lot .... a lot of path problems.

    Happy to see get over it, finally.

  5. qiangwu
    December 1st, 2009 at 17:51 | #5

    Same problem!

    If I leave the route.rb as clean as follows..

    m.resources :pages
    m.root :controller => "pages"
    map.collecti ':controller/:action/:id'
    map.collecti ':controller/:action/:id.:format'

    The clearance module won't give me any trouble for sign_in/sign_out

    But I can't afford not to put something in the route.rb in my final proj.
    Even if I add a single route like:

    m.resources :pages
    m.root :controller => "pages"

    # Here is the trouble-maker
    m.visit_page ':name', :controller => 'viewer', :action => 'visit'

    map.collecti ':controller/:action/:id'
    map.collecti ':controller/:action/:id.:format'

    The "clearance" sign_in/sign_out actions start meddling in Page controller instead of its own "clearance/session" controller when I hit sign_in/out button. I check the log, once I add some route in the route.rb, sign_in/sign_out would meddle in the corresponding Controller instead of its own controller.

  6. qiangwu
    December 1st, 2009 at 18:00 | #6

    Typo: route.rb:

    m.resources :pages
    m.root :controller => "pages"

    # Here is the trouble-maker
    m.visit_page ':name', :controller => 'viewer', :action => 'visit'

    m.collecti ':controller/:action/:id'
    m.collecti ':controller/:action/:id.:format'

  7. qiangwu
    December 1st, 2009 at 19:44 | #7

    Well, problem solved

    I tried all the possible permutation, finally I got it works.

    I need to include the full path of the symbolic url instead of just ":name"
    Like following ...
    ....
    map.view_page '/pages/show/:name', :controller => '/pages', :action => 'show'
    map.visit_page '/alumni/visit/:name', :controller => "/alumni", :action => 'visit'

    ......

    Now the clearance sign feature won't be meddling with other controllers!!

  8. Ben Ho
    December 9th, 2009 at 23:43 | #8

    Hi John
    Is there a way to migrate active accounts into Clearance? I tried to migrate some test accounts for my final project, but all I could manage is insert the email and password. Once I run my migration, the password is automatically encrypted. The account is not activated though. Thus, when I try to log into my project, it says an email will be sent out to confirm the account. Once I click the link, the account is activated.

    Is there a way around this so that I can activate the account through migration? Or for the sample accounts I need to ask the grader to activate it manually?

  9. December 10th, 2009 at 00:07 | #9

    @Ben Ho

    You would have to write a migration where you fix up the users. Something like:

    # for each user u:
    u.confirm_email!

    If you dig into the Clearance code, you will see some methods such as .confirm_email! on their User class/module.

  10. Ben Ho
    December 10th, 2009 at 01:09 | #10

    Ahh, that did the trick. I must have missed that method - thanks.

  11. Ken Busch
    December 11th, 2009 at 14:46 | #11

    I submit my final project with clearance. "That's nice," says my grader, "I can't log into it. It won't send me my confirmation e-mails!"

    If we use clearance, should we make sure that it really will send emails in production?

    Or can we rely on graders to be avid log readers?

  12. December 11th, 2009 at 14:52 | #12

    @Ken Busch

    We would like it if you did. The easiest thing to do is to go to the lecture slides regarding e-mail, and just paste in the config for a GMail user.

  13. December 11th, 2009 at 14:53 | #13

    @Ken Busch

    P.S. We won't die if you don't: We can copy-and-paste the link from the log.

    If you DO leverage GMail, and the sending account is precious to you, you would want to leave your password out, and provide very clear instructions so that your TA can switch the credentials.

  14. Ken Busch
    December 11th, 2009 at 16:10 | #14

    @john

    Okay. I've created a spare email account.

    I had been under the misimpression that one needed to do a ruby script\generate mailer, but now I see that is unnecessary for clearance.

  15. Ken Busch
    December 11th, 2009 at 22:57 | #15

    Now I seem to have gotten stuck in trying to repair the functional tests using clearance.

    I've added a login method (imitating ccc2) and I set @request.cookies['user_id'] to a legitimate user id. (I have a test fixture for users.) The controller references current_user. No matter what I do, that ends up being nil from the functional tests. (It works when I load the page, though.)

    Looking at section 14.3 of AWR (p. 221ff of print), I've also tried passing three parameters to get, e.g.,

    get :index, {}, { :user_id => n }

    where I've tried both "1" and 1 for n. No dice that way either. I thought I saw something about functional tests and clearance, but I forget where.

  16. Ken Busch
    December 12th, 2009 at 14:22 | #16

    @Ken Busch
    Keith provided me a solution. The idea is to put out a cookie for current_user to find. Done as follows from a login method called by the functional tests:

    @request.cookies['remember_token'] = user.remember_token

  17. Alex Kalish
    December 12th, 2009 at 17:37 | #17

    I just realized that I think I'm not integrating Clearance quite right. Am I correct that I should be solely using the controller and views in the unpacked clearance gem? I have a user controller in /app/controllers and user views in /app/views/users/ due to scaffolding, which I have been editing for use. I guess I should remove them? In my application, I've added several attributes to user and need to have index and show actions.

    Any advice would be great, thanks!

  18. December 12th, 2009 at 23:20 | #18

    @Alex Kalish

    You should use your controllers and views as you have defined them (exception in a moment).

    For controllers that need authentication, put

    before_filter :authenticate
    

    at the top. If you consult AWDR, you can find out how to constrain the authentication to only specific actions, etc.

    Another way to do it is to have one controller that has the before_filter, and then have all of your controllers that need authentication subclass that (this is what is done in the screencast).

    Now, the exception is around the User model and User controller: You should be able to change the model all you want in the regular directory (app/models). If you add extra attributes, you will need to add [for example, here for :first_name] "attr_accessible :first_name" a the top of the model for each one.

    For the User controller: You can go into the clearance gem in your vendor directory and actually change the controller and views. My advice is to change the User controller as little as possible. Once you bring the gem into your project, you can do with it what you want.

    There is a working clearance-ified app on the downloads page: http://e168f09.plugh.org/downloads/

  19. Alex Kalish
    December 13th, 2009 at 13:05 | #19

    @john
    Thanks for your response!

    I had figured out the use clearance with before_filter, but your "exception" is exactly what I was looking for. So, I did, unfortunately, scaffold user before installing clearance, so I have a user controller and view that I now do not need. Should I just remove them? Should I use "script/destroy controller User"? Will that mistakenly remove anything clearance added?

    Then.. if I want a show, index and edit actions for users, I can add them to the users_controller in the clearance gem directory. And, I can add views for those actions in the appropriate clearance gem directory as well. Am I understanding this correctly.

    Thanks again!

  1. No trackbacks yet.