Facebook OAuth with Sinatra

This MOSTLY is working, so, here’s what I found when trying to connect to Facebook with OAuth. It needs some serious refactoring because a lot of this was done in an “investigative” trial and error way. It also looks like Facebook have improved their docs on this since I was first looking at them…

First, you need to initiate a connection:

    get '/:blog_name/facebook/oauth/create' do
      redirect "https://graph.facebook.com/oauth/authorize?client_id=##FACEBOOK API KEY##&redirect_uri=##CALLBACK URL##&scope=publish_stream,user_status,user_photos,user_about_me"
    end

where ##FACEBOOK API KEY## = your applications API key (mine is a 32 digit hex value) and ##CALLBACK URL## = the URL that will be processing the next step. Also, the scope value let’s you get more access to the user’s account. Check their extended permissions doc for more info.

Next, you need to process what comes back from Facebook. I am stashing what comes back in the DB (the FacebookOauthToken model). Also using the Mechanize gem which is pretty silly. Some of the code below is specific to my app, so ignore those bits…

    get '/:blog_name/facebook/oauth/callback' do
      if !params['code'].blank?
        url="https://graph.facebook.com/oauth/access_token"
        client_id="client_id=##CLIENT ID##"
        client_secret="client_secret=## CLIENT SECRET ##"
        code="code=#{URI.escape(params['code'])}"
        redirect_uri="redirect_uri=http://example.org/#{@blog.url_name}/facebook/oauth/callback"
 
        url="#{url}?#{client_id}&#{client_secret}&#{code}&#{redirect_uri}"
        begin
          res=open(url)
        rescue
          flash[:error]="There was a problem connecting to Facebook"
          redirect "/#{@blog.url_name}/"
        end
        read=res.read
        access_token=CGI.parse(read)['access_token']
        if !access_token.blank?
          @blog.facebook_oauth_token = FacebookOauthToken.new(:access_token=>access_token,:blog_name=>params[:blog_name])
          if @blog.save
            begin
              a=Mechanize.new
              res=a.get("https://graph.facebook.com/me/?access_token=#{access_token}")
              @blog.facebook_oauth_token.facebook_id=JSON(res.body)['id']
              @blog.save!
            rescue
              flash[:error]="There was a problem getting information from Facebook"
              redirect "/#{@blog.url_name}/"
            end
            flash[:notice]="Facebook connection created!"
            redirect "/#{@blog.url_name}/"
          else
            flash[:error]="There was a problem saving Facebook connection"
            redirect "/#{@blog.url_name}/"
          end
        else
          flash[:error]="There was a problem making Facebook connection"
          redirect "/#{@blog.url_name}/"
        end
      else
        flash[:error]="There was a problem connecting to Facebook"
        redirect "/#{@blog.url_name}/"
      end
    end

Where ##CLIENT ID## = your Application ID (mine is a 12 digit numeric value) and ## CLIENT SECRET ## = your Application Secret (mine is a 32 digit hex value).

Once you have an access_token you should be able to make calls to URLs like this:
"https://graph.facebook.com/me/?access_token=#{access_token}"
. The /me path is the connected user’s data and I’m grabbing their Facebook ID from the returned JSON for use elsewhere.

Posted: May 20th, 2010 | Author: jay | Filed under: Code | Tags: , , , , , , , | 1 Comment »

Curl command to test JSON posts

1
curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X POST -d '{"user":{"username_or_email":"username","password":"password"}}' http://fas/test_json_post
Posted: April 4th, 2010 | Author: jay | Filed under: Code | Tags: , , , , , | No Comments »

Lost sleep over JSON and Rack::PostBodyContentTypeParser

I’ve been fighting this issue the last couple nights. I wrote earlier about how Rack::PostBodyContentTypeParser can automagically turn a posted JSON object into a Rack / Sinatra params hash. So, I wrote some tests to make sure this was the case and moved on. Well, it turns out in real life things weren’t working and I couldn’t figure out why. Everything looked cool, but the hash wasn’t getting set when I did an AJAX call in the browser – everything was empty. I looked at everything, from the server, to the JS library, to the browser, to setting different content types in prototype.js etc… UGH!

The short of it is that Rack::PostBodyContentTypeParser requires exactly application/json in order to automagically turn the posted JSON object into Rack params and prototype.js (and jquery.js were adding an encoding type of charset=UTF-8 so the entire header entry was coming across as this CONTENT_TYPE: application/json; charset=UTF-8. So, as a fix, I’m just including the Rack::PostBodyContentTypeParser in the Sinatra application with one small change. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module Rack
 
  # A Rack middleware for parsing POST/PUT body data when Content-Type is
  # not one of the standard supported types, like <tt>application/json</tt>.
  #
  # TODO: Find a better name.
  #
  class PostBodyContentTypeParser
 
    # Constants
    #
    CONTENT_TYPE = 'CONTENT_TYPE'.freeze
    POST_BODY = 'rack.input'.freeze
    FORM_INPUT = 'rack.request.form_input'.freeze
    FORM_HASH = 'rack.request.form_hash'.freeze
 
    # Supported Content-Types
    #
 
    ################## turned into regex so it matches type with encoding data...
    #APPLICATION_JSON = 'application/json'.freeze
    APPLICATION_JSON = /^application\/json/.freeze
 
    def initialize(app)
      @app = app
    end
 
    def call(env)
      case env[CONTENT_TYPE]
      when APPLICATION_JSON
        env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
      end
      @app.call(env)
    end
 
  end
end

I tested that this worked by writing the following:

1
2
3
4
5
6
7
8
9
10
def test_post_as_json_converts_to_params
  # sanity check that post with normal params works...
  post '/test_params_as_json', :param1=>"param one"
  assert_equal last_response.body,"params[:param1]=param one"
  post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json"
  assert_equal last_response.body,"params[:param1]=param one"
  # this is the problem, adding a charset to the content type seems to breaks rack-contrib/post_body_content_type_parser.rb
  post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json; charset=UTF-8"
  assert_equal last_response.body,"params[:param1]=param one"    
end
Posted: April 4th, 2010 | Author: jay | Filed under: Code | Tags: , , , , , , , , | 4 Comments »

Using Rack middleware to parse JSON

In attempting to AJAX-ize the site, I had the desire to handle JSON as if it were form post data. Queue a Rack middleware solution. rack-contrib contains a bunch of common middleware extensions, one being the horribly named PostBodyContentTypeParser. To get this working I added:

1
require 'rack/contrib'

with all of the rest of the required files.

Added:

1
use Rack::PostBodyContentTypeParser

to my application class

And went about over testing it like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test_json_creates_params_hash
  params_hash={"user"=>{"username"=>"testuser","email"=>"test@test.com","password"=>"pass1","password_confirmation"=>"pass1"}}
  post '/test_json', params_hash
  assert !last_request.params.blank?
  assert_equal params_hash, last_request.params
  assert last_response.ok?
  json_string="{\"user\":{\"password_confirmation\":\"pass1\",\"username\":\"testuser\",\"password\":\"pass1\",\"email\":\"test@test.com\"}}"
  post '/test_json', JSON(json_string)
  assert !last_request.params.blank?
  assert_equal params_hash, last_request.params
  assert last_response.ok?
  post '/test_json', json_string, "CONTENT_TYPE"=>"application/json"
  assert_equal last_request.env["CONTENT_TYPE"], "application/json"
  assert !last_request.params.blank?
  assert_equal params_hash, last_request.params
  assert last_response.ok?
end
Posted: March 30th, 2010 | Author: jay | Filed under: Code | Tags: , , , , , , , | No Comments »