Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
John Barker

Facebook and GooglePlus Javascript SDK sign in with Devise + RoR

John Barker
Friday, January 18, 2013

Recently I added a modal sign in and sign up dialog to a Rails application that allowed for sign in using Facebook or Google as well as via email. This dialog can appear any time a user attempts to perform a protected action, allowing them to sign in and continue without losing any data.

To make this work I had to implement Google and Facebook sign in using the new javascript SDK provided for both platforms. The old style authentication redirects when successful which means any in memory session state is completely lost. This means forms are cleared, event handlers are rebound and any work in progress has to be done again.

Before I explain the difficulties we had getting this to work with I’ll explain briefly how OAuth works with respect to devise.

Typical OAuth workflow

Most sites implement this kind of workflow by opening a popup window pointing at their oauth request url (e.g: /oauth/facebook/). This sets up the initial state of the session using a cookie and redirects to the login page providing a url to it to redirect the user again once they’ve authenticated.

If authentication is successful, an oauth token is generated and stored in a cookie and the user is redirected to the callback url. The callback url hits the devise stack and verifies that the token is real by asking Facebook to verify it. If everything checks out execution dips into your application code and the user is created or looked up by some identifiable piece of information.

Client Side workflow

The new SDKs provided by Facebook and Google have changed this and allow for much greater flexibility. Facebook documents its new login workflow here http://developers.facebook.com/docs/howtos/login/getting-started/ and Google documents theirs here: https://code.google.com/p/google-api-javascript-client/wiki/Authentication.

Both APIs have a simple method which expects a callback. The callback is executed indicating whether authentication was successful and it’s up to you what you do with that information. This allows for greater flexibility and smoother transitions into an authenticated step.

Getting it to work with Devise

There are a number of gotchas with using the client side approach, some of them related to possible bugs and my difficulty in interpreting just how the OAuth devise gems work.

OAuth Gem Version

Omiauth OAuth2 version 1.1 recently introduced CSRF validation for the authentication workflow. Unfortunately this breaks client side validation since there is no request component.

For now I suggest downgrading those gems like so:

# NOTE: omniauth-oauth2 breaks client authentication, see here:
# https://github.com/intridea/omniauth-oauth2/issues/20
gem 'omniauth-oauth2', '~> 1.0.0'
# NOTE: omniauth-facebook 1.4.1 breaks SDK authentication, see here:
# https://github.com/mkdynamic/omniauth-facebook/issues/73
gem 'omniauth-facebook', '= 1.4.0'

The comments will give any future reader of your Gemfile an indication of what they need to do to lift the version restrictions on the omniauth dependencies.

Google OAuth2 Token Validation

The current omniauth-google-oauth2 gem will try to validate your access token with a different server and request format to the one required by the new javascript SDK. For the time being you can use our the forked version (here)[https://github.com/pivotal-geostellar/omniauth-google-oauth2/tree/client_login].

Performing Authentication

For the Facebook SDK I authenticate like so:

FB.login(function(response) {
  if(response.authResponse) {
    $.ajax({
      type: 'POST',
      url: '/users/auth/facebook/callback',
      dataType: 'json',
      data: {signed_request: response.authResponse.signedRequest}
      success: function(data, textStatus, jqXHR) {
        // Handle success case
      },
      error: function(jqXHR, textStatus, errorThrown {
        // Handle error case
      })});
  }
, scope: 'email'});

For Google:

var scope = 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile';
gapi.auth.authorize({client_id: 'client id', scope: scope}, function(result) {
    if(result && !result.error) {
      $.ajax({
        type: 'POST',
        url: '/users/auth/google_oauth2/callback',
        data: {code: result},
        dataType: 'json',
        success: function(data, textStatus, jqXHR) {
          // Handle authorized case
        },
        error: function(jqXHR, textStatus, errorThrown) {
          // Handle error case
        }
      });
    } else {
      // Handle unauthorized case
    }
});

Both of these calls hit the OAuth callback endpoint to verify the access tokens obtained by the user. If authentication succeeds you’ll get the typical devise+OAuth workflow and a session omniauth.auth cookie with the appropriate details.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Interacting with popup windows in Cucumber/Selenium

Pivotal Labs
Monday, October 24, 2011

OAuth providers like LinkedIn often pop-up in a new browser window rather than in Javascript so that the user entering their credentials can see the location bar to be sure they are not being phished by the website requesting their credentials. This is great for security, but not so great for Cucumber testing.

features/signup.feature

Scenario: Sign Up with LinkedIn
  When I go to the home page
  And I follow "Sign Up"
  And I grant LinkedIn access
  Then I should be on the new user page

My application has a hyperlink that opens the OAuth login on the OAuth provider’s website in a new window. Let’s presume the simple matter of wiring this up is already coded in my view.

Testing this with Cucumber requires telling the Selenium web driver to interact with the new popup window. We can do this using page.driver.browser.window_handles to find the newest window handle and scoping out actions to that window.

features/support/signup_steps.rb

When /^I grant LinkedIn access$/ do
  begin
    main, popup = page.driver.browser.window_handles
    within_window(popup) do
      fill_in("Email", :with => "newlee@pivotallabs.com")
      fill_in("Password", :with => "password")
      click_on("Ok, I'll Allow It")
    end
  rescue
  end
end

And that’s it!

Keep in mind that if you use this test as-is, you will be hitting LinkedIn on the real Internet. This is great if you want a test that will always verify the real API, but not so good for CI, since it is Internet connection-dependent and slow. Consider using something like VCR or Artifice to stub out your service calls.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Will Read

LinkedIn Gem for a Web App

Will Read
Monday, December 7, 2009

There’s an untapped cash cow out there when it comes to recruiting and her name is LinkedIn. Until recently, only LinkedIn had access to your profile and social graph, but all that changed with the release of their OAuth-based API. I’f you’ve hooked into Twitter or Google then this authentication process should feel very familiar to you. To help you along, pengwynn released a LinkedIn gem last week.

What’s lacking is a good controller example, you kind of have to piece it together yourself. So code first, explanation second. Here’s what a simple LinkedIn authentication controller might look like:

<code>
#uncomment the line below if you aren't using bundler
#require 'rubygems'
require 'linkedin'

class AuthController < ApplicationController

  def index
    # get your api keys at https://www.linkedin.com/secure/developer
    client = LinkedIn::Client.new("your_api_key", "your_secret")
    request_token = client.request_token(:oauth_callback =>
                                      "http://#{request.host_with_port}/auth/callback")
    session[:rtoken] = request_token.token
    session[:rsecret] = request_token.secret

    redirect_to client.request_token.authorize_url

  end

  def callback
    client = LinkedIn::Client.new("your_api_key", "your_secret")
    if session[:atoken].nil?
      pin = params[:oauth_verifier]
      atoken, asecret = client.authorize_from_request(session[:rtoken], session[:rsecret], pin)
      session[:atoken] = atoken
      session[:asecret] = asecret
    else
      client.authorize_from_access(session[:atoken], session[:asecret])
    end
    @profile = client.profile
    @connections = client.connections
  end
end
</code>

So the flow your user sees is this:

  • visit /auth/ which automatically redirects me to log in via LinkedIn
  • type in my LinkedIn user name and password, click submit
  • submitting takes me to /auth/callback which might show my profile and connections

What the code does:

  • creates a new LI client based on your credentials as a LinkedIn developer
  • get a request token (pay attention, there’s 2 kinds) , and sets the callback to your callback action
  • saves off the request token-token and the request token-secret into the session, you’ll need to reconstruct the request token in the callback, and the request token itself doesn’t have a marshal-load method defined.
  • sends the user off to the LinkedIn log in page
    — user logs in and is send to the callback action —
  • create a new LI client with the developer key and secret from LinkedIn
  • check to see if the access token (2nd kind of token) has been created before
  • create the access token using the oauth_verifier sent from LinkedIn and the old request token (1st kind) that you save in the session.
  • authorize the client and save off the access token info so you don’t have to send your user back to LI when he comes back (*here I save the access token into the session for simplicity in the example, you should put this in a database probably)
  • if the access token exists, just authorize the client usoing the access token
  • finally, use the client to do interesting things like show the user his profile and his connections.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (778)
  • rails (113)
  • testing (86)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (53)
  • techtalk (44)
  • rspec (38)
  • activerecord (29)
  • productivity (29)
  • gogaruco (29)
  • ironblogger (29)
  • git (28)
  • nyc (27)
  • rubymine (25)
  • mobile (21)
  • cucumber (20)
  • bloggerdome (19)
  • process (19)
  • pivotal tracker (19)
  • design (18)
  • jasmine (18)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • tracker ecosystem (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • gem (13)
  • bdd (13)
  • selenium (12)
  • css (12)
  • goruco (12)
  • bundler (12)
  • tdd (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • rubygems (9)
Subscribe to oauth Feed
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >