Nicolas Alpi, Web developer

A blog about productivity, startups and me.

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:


  • A user should still be able to signin/signup with Clearance

  • A user should be able to create a new account throught the RPX widget

  • A user should be able to sign in throught the RPX widget

  • If an existing user sign up via the RPX widget, with an existing email, it should merge the informations

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
  1. We are using the clearance sign_in method to do the following behaviour
  2. session[:user_id] = @user.id
  3. @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 => "testgmail.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 => "heygmail.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