I saw Clickpass in action at the Hacker News, and I thought it was another great way to reduce another login account. I wanted to implement it for my Open Translation project.
However, when I tried to find tutorials for using Clickpass with a Rails app, I couldn’t find any! How could it be! The pesudo code example Clickpass provided was for Java, I think, and thus it wasn’t any help to me. I was completely lost.
Then I realized that in the core underlying architecture of Clickpass is OpenID. Then it all made sense to me. I found a great tutorial on OpenID and Authlogic on a Railscasts episode (I am not using Authlogic for my site, though, but for the Open Translation project, I probably will.). So the following is a mixture of the Railscasts episode, ruby-openid gem, open_id_authentication plugin and Clickpass setup.
1. Install the gem and the plugin.
sudo gem install ruby-openid
Add the following line to config/environment.rb
config.gem "ruby-openid", :lib => "openid"
Install the plugin.
ruby script/plugin install git://github.com/rails/open_id_authentication.git
Run the migration file for open_id_authentication to create an authentication table. You can use file method, but I found this to be easier.
rake open_id_authentication:db:create
2. At this point, it’s good to create a column for storing openid URL in users table.
ruby script/generate migrate AddOpenIDURLtoUserModify the migration file to look like the following.
class AddOpenIdurLtoUser < ActiveRecord::Migration def self.up add_column :users, :openid_url, :string end def self.down remove_column :users, :openid_url end end
Run
rake db:migrate
3. Now you are ready to get down to writing actions. There is Clickpass operation flow chart. You should definitely check it out. You should also read through their documentation, but don’t worry about actual codes.
3.5. Since I am going to have three actions in session controllers that does those three actions required by Clickpass, I need to add them in the config/routes.rb.
Log users in == begin_openid_login and complete_openid_login in Clickpass == begin_openid_login action my session controller
Merge accounts == add_openid_to_user in Clickpass == add_openid action in my session controller
Register new sign-up == process_openid_registration in Clickpass == openid_reg action in my session controller
Thus, in config/routes.rb,
map.resource :session, :member => {:destroy => :get, :begin_openid_login => :get, :add_openid => :post, :openid_reg => :get}
3.75. Also don’t forget to take these actions off of forgery protection list.
class SessionsController < ApplicationController protect_from_forgery :except => [:begin_openid_login, :add_openid]
4. Clickpass provides a chunk of Javascript code for login button. When a user clicks on it, it will try to authenticate the user with either OpenID or other credentials. The first you have to do is what to do with the return value, which is OpenID URL. That’s how an OpenID user is recognized, by OpenID URL.
5. At this point, go to Clickpass’ developer page, and create a new site ==> Click on the “Create a new site” button. Pick a name for your site. Don’t worry about other fields. And click on “Save Changes” button and then “Next: Log users in” button.
6. Now it gets interesting. There is the bunch of Javascript code I mentioned earlier. You can copy and paste it in the view where you want user to login. For me, I put it in app/views/layouts/application.html.erb right below Facebook Connect button. Why am I keeping the Facebook Connect button when Clickpass also works with FB credentials? I will explain that later.
Here begin_openid_login is important, since this parameter tells Clickpass where to send the openid_url. I have session controller that takes care of session creation and deletion, it was a natural place. So, for my production site, I have the following.
begin_openid_login = http://www.playgroundrus.com/session/begin_openid_login
In development environment, just use localhost as your host name.
OpenID parameter label is another important parameter. This defines how OpenID URL is referred in your app. For me, I have
OpenID parameter label = openid_url
Set the submission method to GET and save changes.
7. Now fun begins. Most of the following is modification of sample code in open_id_authentication. In my session controller, I have the following actions configured.
The following takes OpenID URL in openid_url variable and send it to open_id_authentication function.
#Begin OpenID authentication def begin_openid_login openid_url = params[:openid_url] open_id_authentication end
Here, open_id_authentication uses openid_url to authenticate a user and if it’s successful, I try to find the user in my users table (remember the openid_url column I created earlier?). If it’s not found, I redirect the user back to Clickpass, where Clickpass will ask user two things. If user already has an account in my site, s/he can decide to merge two accounts. If user is completely new, Clickpass will call another action in session controller to create a user in my users table. I will get to it later.
protected def open_id_authentication authenticate_with_open_id do |result, openid_url| if result.successful? if @current_user = User.find_by_openid_url(openid_url) successful_login else #If users is not found based on OpenID, Clickpass will ask your if new account should be opened or merged redirect_to "http://www.clickpass.com/process_new_openid?site_key=aBIC63MAFp&process_openid_registration_url=http%3A%2F%2Fplaygroundrus.com%2Fsession%2Fopenid_reg&requested_fields=nickname%2Cemail&required_fields=nickname%2Cemail&nickname_label=Nickname&email_label=Email" end else failed_login result.message end end end private def successful_login session[:user_id] = @current_user.id redirect_to(root_url) end def failed_login(message) flash[:error] = message redirect_to(new_session_url) end
You can create a fake user with “http://clickpass.com/public/always_authenticate” in the openid_url column. And if you click on “submit authenticated OpenID” in step 2 of the page, user should be logged in successfully. If it happens, congrats! Take a breather. That was easy, wasn’t it?
8. Next is merging accounts. If an existing user signs up with Clickpass or signs in through Clickpass, because this user’s OpenID URL is not in the table, I won’t be able to find the user. When the user is sent back to Clickpass, s/he can choose to merge existing account with Clickpass account. So, for me,
begin_add_openid_to_user URL = http://playgroundrus.com/session/add_openid
user_id label = username (I think there is a bug. Even with this, Clickpass uses user_id as hash key)
In my session controller, I first find a user using username and password, and just add the openid_url.
#MERGE: Add openid_url to exisitng user def add_openid if @current_user = User.find_by_username_and_hashed_password(params[:user_id], encrypt(params[:password])) @current_user.openid_url = params[:openid_url] @current_user.save successful_login else redirect_to "#{params[:clickpass_merge_callback_url]}?userid_authenticated=false" end end
You should be able to delete openid_url from the fake user account, and test the merging in the step 2 of the page. It should work.
9. Next is, what to do if the user is really new. Then we create an account.
process_openid_registration_url = http://playgroundrus.com/session/openid_reg
I only care about username and email address, so I only had nickname and email fields checked.
The long URL you see in the top gray box is the one you use to redirect a user when no current user is found. It’s the same code earlier in step 7.
In my session controller, I create a new user with the nickname and email.
#NEW def openid_reg @current_user = User.new if user=User.find_by_username(params[:nickname]) || user=User.find_by_email(params[:email]) failed_login("Userrname or email is already taken!") end @current_user.username = params[:nickname] @current_user.email = params[:email] @current_user.openid_url = params[:clickpass_openid] #Force save @current_user.save(false) successful_login end
11. That should have you pretty much covered for working with Clickpass. One major drawback of Clickpass is that I won’t know if a Clickpass user is also a Facebook user. I have Facebook Feed publishing configured so that any new addition or edition of a playground will trigger Facebook Feed if logged in user is a Facebook user. With >300M users and strong social network, Facebook Feed is very valuable for spreading words…. That’s why I still have Facebook Connect button along with Clickpass login button. I REALLY hope Clickpass can change add that feature soon. Other than that, it’s pretty cool stuff.
Pingback: Clickpass » Blog Archive » New Rails Tutorial on installing Clickpass
#1 by Robert on October 17th, 2009
Quote
Very good tutorial, I used it to provide clickpass to project eureka.
On a second note, couple of changes I needed to do(i present them here in case more ppl are using it), here are my diffs:
redirect_to “#{params[:clickpass_merge_callback_url]}?userid_authenticated=false” => there is a typo here it should be “&” instead of “?”
Also for new users instead of redirecting them new_session_url(when userid or email is taken) I prefer to redirect them to clickpass:
def openid_reg
if user=User.find_by_login(params[:nickname]) || user=User.find_by_email(params[:email])
redirect_to “#{REDIRECT}®istration_succeeded=false&nickname_error=Username%20invalid%20or%20taken”
return
….
#2 by Nita Turrell on September 22nd, 2011
Quote
“I only care about username and email address, so I only had nickname and email fields checked.”
And so does everybody. Those two items are the only important things.
#3 by Ronny Jo on January 26th, 2012
Quote
These are very great tips! Since you taught me something new today I will share one with you….you can bid on electronics and other “man toys” on an online auction site at http://www.outlawbidder.com. It is a live auction and you basically get to name your own price on things like electronics, XBOX, Kinect, TV’s, radar detectors, shotguns, rifles, fishing equipment, camping equipment and more!