Nicolas Alpi

Hi, Salut

Pragmatic web application developer, I enjoy my freelance way of life in my home office every day. I mainly use Ruby based frameworks (Rails / Sinatra / Rack) as my main development arsenal and jQuery is my day to day friend for every piece of client side. Aside from development freedom, I really enjoy the business side of behind freelance, and will soon to release my own personal application. In my free time, I enjoy cooking, runnning and sharing a coffee or a beer with people, so if you're around Bristol, let's be in touch.


Implementing RPX with clearance in Ruby on rails in 5 minutes

When it comes to user engagement, having your users being able to create an account or login with one click is a really “nice to have” feature.

This is where RPX appears to be able a really neat solution.

It allows your users to signin/signup to your website, througth the RPX widget, using their preferred provider (Google/OpenId, Facebook, …).

You can consult the RPX website for a complete list of supported provider.

Expectations

I’ll assume that you already have Clearance setup properly in your app. I presume that this tutorial can be used as a blueprint for implemennting RPX with others authentication system in Ruby on Rails.

What do we want to achieve:

Installing rpx_now gem

First you’ll need to create an account on the RPX website. Then create your application in their application manager.

You’ll be given an access key.

Rpx_now gem is certainly the best RPX API implementation available out there.

Install it as you usually do for a gem in your Rails application, using bundler or config.gem.

Create a migration, use the migration code bellow and then run your db:migrate.

class AddIdentifierToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :identifier, :string
  end

  def self.down
    remove_column :users, :identifier
  end
end

We are adding the identifier column to the user table in order be know from where the users come from.

Add the RPX key in your config/environment.rb:

config.after_initialize do # so rake gems:install works
    RPXNow.api_key = "YOU RPX API KEY"
end

And you’re now ready to implement the code.

New user and login with RPX

RPX doesn’t know if the user already exists in your app database or not. So handling a new user creation or login a user will use the same method.

This method is the one you specify when rendering the RPX widget inside the new session or new user views. Let’s call it rpx_token

The browser will be redirected to the rpx_token method after the RPX login process. It will be posted a token argument and will call the RPX api to retrieve the user profile informations.

Add the RPX_now embed_code method is your new session view and new user view (outside of the form).

[Create an helper is a good idea]

RPXNow.embed_code('MyRPXApplicationName', rpx_token_session_url)

You can see that we are using the rpx_token_session_url route.

This will need to be define in your config/routes.rb

 map.resource  :session,
                     :controller => 'sessions',
                     :member     => {:rpx_token => [:post] }

Then in your sessions_controller add an rpx_token method:

def rpx_token
    raise ActionController::Forbidden unless data = RPXNow.user_data(params[:token])

    @user = User.find_by_identifier(data[:identifier]) || User.find_by_email(data[:email])
    if @user.blank?
      @user = User.new(:username => data[:username],:email => data[:email],:identifier => data[:identifier])
      @user.email_confirmed = 1
      @user.save
    end
    # We are using the clearance sign_in method to do the following behaviour
    #    session[:user_id] = @user.id
    #    @current_user = @user  

    sign_in(@user)

    redirect_to root_path
end

If we were only using RPX to handle user login, that would be our last step there.

But because when want to make it play it nicely with Clearance, we’ll need an extra step.

If you try to use it at this point, Clearance will complain that the new user doesn’t have any password. Fortunately there is an existing Clearance method called password_required?

So in our user model we’ll need to super seed this method in order to add our requirement.

#models/user.rb
  ...

  def password_required?
    super && identifier.blank?
  end

  ...

Now, if there is an identifier supplied we can create the new account without password.

[Be sure that you have your tests for not being able to login is password is blank ;)].

At this point you can now create a user, login an existing user or a new user all with RPX and Clearance.

The tests

To test that I used Shoulda and Mocha.

#session_controller_tests

context "on GET :new" do
    should "Display RPX widget" do
      RPXNow.expects(:embed_code).with('MyRPXAppName', rpx_token_session_url)
      get :new
    end
end

  context "Connection via RPX" do
    context "from a non existing user" do

      setup do
        RPXNow.expects(:user_data).with("123456").returns({:email => "rpxuser@email.com",:identifier => "test",:username => "rpxusername"})
        post :rpx_token, :token => "123456"
      end
    
      should_change "User.count", :by => 1

      should "create the user with the RPX data" do
        assert assigns(:user).email      == "rpxuser@email.com"
        assert assigns(:user).identifier == "test"
        assert assigns(:user).username   == "rpxusername"
      end

      should "set the session with the newly created user informations" do
        assert @request.session[:user_id] == assigns(:user).id
      end

      should "confirm the email on the user creation" do
        assert assigns(:user).email_confirmed == true
      end

      should_redirect_to("the root page") { root_path }
    end


    context "from an existing user" do
      setup do
        @user = Factory(:user)
        RPXNow.expects(:user_data).with("123456").returns({:email => @user.email,:identifier => "test",:username => "rpxusername"})
        post :rpx_token, :token => "123456"
      end

      should "set the session with the user informations" do
        assert @request.session[:user_id] == @user.id
      end
      should "not create a second user" do
        assert User.count == 1
      end
    end

    context "with an invalid token" do
      setup do
        RPXNow.expects(:user_data).with("invalidtoken").returns(false)
        post :rpx_token, :token => "invalidtoken"
      end
      should_respond_with :forbidden
      should_not_change "User.count"
    end
#unit/usert_test.rb

context "A user" do

...

    should "not request set password_requiered? to false if an identifier is set" do
      @user.identifier = "test"
      assert !@user.password_required?
    end
    
    should "set password_required? to true is identifier is not set" do
      @user.identifier = ""
      assert @user.password_required?
    end

    should "be able to create a user without password if identifier is set" do
      @new_user = User.new(:email => "test@gmail.com",:identifier => "test")
      assert @new_user.save
    end

    should "not be able to create a user without a password if identifier is not set" do
      @new_user2 = User.new(:email => "hey@gmail.com")
      assert !@new_user2.save
    end


...

end

Conclusion

I haven’t play with the PRO features yet, but rpx_now is really simple to implement and it’s really simple to have it working along Clearance.

I hope it was useful. Let me know if you had any trouble with implementing it.


Comments

blog comments powered by Disqus