I can navigate to the main url which successfuly redirects to facebook. I grant permission, and I am redirected back to the callback url. If this url simply returns something like 'hello', it works fine no errors. But calling token = client.auth_code.get_token(@data[:code], :redirect_uri => redirect_uri)
causes the error.
Ok finally got this to work. The error being reported was just some weird thing with error handling and had nothing to do with the actual problem. The problem was that the oauth2 gem is generic and you have to taylor a few things to make it work with facebook. These are the things you have to do that differ from the readme (see issues 70 and 75 on github for more info)
Before you create your client, you must register a parser for the facebook response:
OAuth2::Response.register_parser(:facebook, 'text/plain') do |body|
token_key, token_value, expiration_key, expiration_value = body.split(/[=&]/)
{token_key => token_value, expiration_key => expiration_value, :mode => :query, :param_name => 'access_token'}
end
You also have to set the token url for the client on creation:
@client = OAuth2::Client.new(ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], {:site => 'https://graph.facebook.com', :token_url => '/oauth/access_token'})
When the oauth receives a response, it uses the parser you tell it to use to parse the response into a hash. The custom :facebook parser ensures that the hash contains the access token and expires string and tells it to use a mode of query and that the param name is access_token. Without the mode and param_name the Oauth client would attempt to send the access token in a header rather than a query string when accessing resources. Facebook expects the access token to be in the url. Without param_name, the oauth client sends it as https://graph.facebook.com/bearer_token=ABC. With the param_name, it is https://graph.facebook.com/access_token=ABC
Finally when you create your AccessToken object, be sure to tell it to use your custom parser like so:
token = client.auth_code.get_token(@data[:code], {:redirect_uri => redirect_uri, :parsed => :facebook})
Altogether it looks like:
require 'sinatra'
require 'oauth2'
require 'json'
class App < Sinatra::Base
configure do
set :views_folder, File.join($BP, 'views')
set :public_folder, File.join($BP, 'public')
end
before do
@data = JSON.parse(request.env["rack.input"].read) if request.request_method =~ /POST|PUT|DELETE/i
@data = params if request.request_method == 'GET'
end
before do
pass if (request.path_info == '/auth/facebook' || request.path_info == '/auth/facebook/callback')
redirect to('/auth/facebook') unless self.logged_in
end
get "/" do
request.request_method
end
def client
if !@client
OAuth2::Response.register_parser(:facebook, 'text/plain') do |body|
token_key, token_value, expiration_key, expiration_value = body.split(/[=&]/)
{token_key => token_value, expiration_key => expiration_value, :mode => :query, :param_name => 'access_token'}
end
@client = OAuth2::Client.new(ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], {:site => 'https://graph.facebook.com', :token_url => '/oauth/access_token'})
end
@client
end
get '/auth/facebook' do
redirect client.auth_code.authorize_url(
:redirect_uri => redirect_uri,
:scope => 'email'
)
end
get '/auth/facebook/callback' do
token = client.auth_code.get_token(@data[:code], {:redirect_uri => redirect_uri, :parsed => :facebook})
user = token.get('/me').parsed
create_user user unless user_exists user
end
def redirect_uri
uri = URI.parse(request.url)
uri.path = '/auth/facebook/callback'
uri.query = nil
uri.to_s
end
end