How to integrate Clickpass (and OpenID) with a Rails app

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.

Post to Twitter

10 thoughts on “How to integrate Clickpass (and OpenID) with a Rails app

  1. Pingback: Clickpass » Blog Archive » New Rails Tutorial on installing Clickpass

  2. 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}&registration_succeeded=false&nickname_error=Username%20invalid%20or%20taken”
    return
    ….

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

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

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>