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 AddOpenIDURLtoUser
Modify 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
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
….
“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.
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!
great points altogether, you simply received a logo new reader.
What may you suggest about your post that you made some days in the past?
Any sure?
whoah this weblog is magnificent i really like studying
your articles. Keep up the great work! You realize,
a lot of persons are looking round for this information, you could aid them greatly.
Aw, this was a really nice post. Taking the time and actual effort to
create a good article… but what can I say… I hesitate a whole lot and never manage to get nearly anything done.
I do trust all of the ideas you’ve presented to your post. They’re very convincing and can definitely
work. Nonetheless, the posts are too short for starters.
May you please lengthen them a bit from next time? Thank you for
the post.
Wow that was strange. I just wrote an incredibly long comment but after
I clicked submit my comment didn’t show up. Grrrr… well I’m not
writing all that over again. Anyways, just wanted to say
wonderful blog!
I like this blog so significantly, saved to my bookmarks .