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: access token, api, client id, facebook, graph, graph.facebook.com, json, oauth | 1 Comment »
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: charset, json, params, PostBodyContentTypeParser, prototype.js, rack, regex, sinatra, test | 4 Comments »
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:
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: json, middleware, params, PostBodyContentTypeParser, rack, ruby, sinatra, testing | No Comments »