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
- 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.