omniauth-facebook 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omniauth-facebook might be problematic. Click here for more details.
- data/Gemfile +2 -0
- data/README.md +15 -1
- data/example/config.ru +64 -1
- data/lib/omniauth/facebook/version.rb +1 -1
- data/lib/omniauth/strategies/facebook.rb +68 -1
- data/spec/omniauth/strategies/facebook_spec.rb +66 -1
- metadata +10 -10
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
This gem contains the Facebook strategy for OmniAuth 1.0.
|
4
4
|
|
5
|
-
Supports the OAuth 2.0 server-side
|
5
|
+
Supports the OAuth 2.0 server-side and client-side flows. Read the Facebook docs for more details: http://developers.facebook.com/docs/authentication
|
6
6
|
|
7
7
|
## Installing
|
8
8
|
|
@@ -26,6 +26,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
26
26
|
end
|
27
27
|
```
|
28
28
|
|
29
|
+
See a full example of both server and client-side flows in the example Sinatra app in the `example/` folder above.
|
30
|
+
|
29
31
|
## Configuring
|
30
32
|
|
31
33
|
You can configure several options, which you pass in to the `provider` method via a `Hash`:
|
@@ -88,6 +90,12 @@ Here's an example *Authentication Hash* available in `request.env['omniauth.auth
|
|
88
90
|
|
89
91
|
The precise information available may depend on the permissions which you request.
|
90
92
|
|
93
|
+
## Client-side Flow
|
94
|
+
|
95
|
+
The client-side flow supports parsing the authorization code from the signed request which Facebook puts into a cookie. This means you can to use the Facebook Javascript SDK as you would normally, and you just hit the callback endpoint (`/auth/facebook/callback` by default) once the user has authenticated in the `FB.login` success callback.
|
96
|
+
|
97
|
+
See the example Sinatra app under `example/` for more details.
|
98
|
+
|
91
99
|
## Supported Rubies
|
92
100
|
|
93
101
|
Actively tested with the following Ruby versions:
|
@@ -97,6 +105,12 @@ Actively tested with the following Ruby versions:
|
|
97
105
|
- MRI 1.8.7
|
98
106
|
- JRuby 1.6.5
|
99
107
|
|
108
|
+
*NB.* For JRuby, you'll need to install the `jruby-openssl` gem. There's no way to automatically specify this in a Rubygem gemspec, so you need to manually add it your project's own Gemfile:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
gem 'jruby-openssl', :platform => :jruby
|
112
|
+
```
|
113
|
+
|
100
114
|
## License
|
101
115
|
|
102
116
|
Copyright (c) 2011 by Mark Dodwell
|
data/example/config.ru
CHANGED
@@ -2,10 +2,73 @@ require 'bundler/setup'
|
|
2
2
|
require 'sinatra/base'
|
3
3
|
require 'omniauth-facebook'
|
4
4
|
|
5
|
+
SCOPE = 'email,read_stream'
|
6
|
+
|
5
7
|
class App < Sinatra::Base
|
8
|
+
# server-side flow
|
6
9
|
get '/' do
|
10
|
+
# NOTE: you would just hit this endpoint directly from the browser
|
11
|
+
# in a real app. the redirect is just here to setup the root
|
12
|
+
# path in this example sinatra app.
|
7
13
|
redirect '/auth/facebook'
|
8
14
|
end
|
15
|
+
|
16
|
+
# client-side flow
|
17
|
+
get '/client-side' do
|
18
|
+
content_type 'text/html'
|
19
|
+
# NOTE: when you enable cookie below in the FB.init call
|
20
|
+
# the GET request in the FB.login callback will send
|
21
|
+
# a signed request in a cookie back the OmniAuth callback
|
22
|
+
# which will parse out the authorization code and obtain
|
23
|
+
# the access_token. This will be the exact same access_token
|
24
|
+
# returned to the client in response.authResponse.accessToken.
|
25
|
+
<<-END
|
26
|
+
<html>
|
27
|
+
<head>
|
28
|
+
<title>Client-side Flow Example</title>
|
29
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js" type="text/javascript"></script>
|
30
|
+
</head>
|
31
|
+
<body>
|
32
|
+
<div id="fb-root"></div>
|
33
|
+
|
34
|
+
<script type="text/javascript">
|
35
|
+
window.fbAsyncInit = function() {
|
36
|
+
FB.init({
|
37
|
+
appId : '#{ENV['APP_ID']}',
|
38
|
+
status : true, // check login status
|
39
|
+
cookie : true, // enable cookies to allow the server to access the session
|
40
|
+
oauth : true, // enable OAuth 2.0
|
41
|
+
xfbml : true // parse XFBML
|
42
|
+
});
|
43
|
+
};
|
44
|
+
|
45
|
+
(function(d) {
|
46
|
+
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
|
47
|
+
js = d.createElement('script'); js.id = id; js.async = true;
|
48
|
+
js.src = "//connect.facebook.net/en_US/all.js";
|
49
|
+
d.getElementsByTagName('head')[0].appendChild(js);
|
50
|
+
}(document));
|
51
|
+
|
52
|
+
$(function() {
|
53
|
+
$('a').click(function(e) {
|
54
|
+
e.preventDefault();
|
55
|
+
|
56
|
+
FB.login(function(response) {
|
57
|
+
if (response.authResponse) {
|
58
|
+
$.get('/auth/facebook/callback');
|
59
|
+
}
|
60
|
+
}, { scope: '#{SCOPE}' });
|
61
|
+
});
|
62
|
+
});
|
63
|
+
</script>
|
64
|
+
|
65
|
+
<p>
|
66
|
+
<a href="#">Connect to FB</a>
|
67
|
+
</p>
|
68
|
+
</body>
|
69
|
+
</html>
|
70
|
+
END
|
71
|
+
end
|
9
72
|
|
10
73
|
get '/auth/:provider/callback' do
|
11
74
|
content_type 'application/json'
|
@@ -21,7 +84,7 @@ end
|
|
21
84
|
use Rack::Session::Cookie
|
22
85
|
|
23
86
|
use OmniAuth::Builder do
|
24
|
-
provider :facebook, ENV['APP_ID'], ENV['APP_SECRET'], :scope =>
|
87
|
+
provider :facebook, ENV['APP_ID'], ENV['APP_SECRET'], :scope => SCOPE
|
25
88
|
end
|
26
89
|
|
27
90
|
run App.new
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'omniauth/strategies/oauth2'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
2
4
|
|
3
5
|
module OmniAuth
|
4
6
|
module Strategies
|
@@ -58,10 +60,26 @@ module OmniAuth
|
|
58
60
|
end
|
59
61
|
|
60
62
|
def build_access_token
|
61
|
-
super.tap do |token|
|
63
|
+
with_authorization_code { super }.tap do |token|
|
62
64
|
token.options.merge!(access_token_options)
|
63
65
|
end
|
64
66
|
end
|
67
|
+
|
68
|
+
# NOTE if we're using code from the signed request cookie
|
69
|
+
# then FB sets the redirect_uri to '' during the authorize
|
70
|
+
# phase + it must match during the access_token phase:
|
71
|
+
# https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348
|
72
|
+
def callback_url
|
73
|
+
if @authorization_code_from_cookie
|
74
|
+
''
|
75
|
+
else
|
76
|
+
if options.authorize_options.respond_to?(:callback_url)
|
77
|
+
options.authorize_options.callback_url
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
65
83
|
|
66
84
|
def access_token_options
|
67
85
|
options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
@@ -73,15 +91,64 @@ module OmniAuth
|
|
73
91
|
params[:scope] ||= DEFAULT_SCOPE
|
74
92
|
end
|
75
93
|
end
|
94
|
+
|
95
|
+
def signed_request
|
96
|
+
@signed_request ||= begin
|
97
|
+
cookie = request.cookies["fbsr_#{client.id}"] and
|
98
|
+
parse_signed_request(cookie)
|
99
|
+
end
|
100
|
+
end
|
76
101
|
|
77
102
|
private
|
78
103
|
|
104
|
+
# picks the authorization code in order, from:
|
105
|
+
# 1. the request param
|
106
|
+
# 2. a signed cookie
|
107
|
+
def with_authorization_code
|
108
|
+
if request.params.key?('code')
|
109
|
+
yield
|
110
|
+
else code_from_cookie = signed_request && signed_request['code']
|
111
|
+
request.params['code'] = code_from_cookie
|
112
|
+
@authorization_code_from_cookie = true
|
113
|
+
begin
|
114
|
+
yield
|
115
|
+
ensure
|
116
|
+
request.params.delete('code')
|
117
|
+
@authorization_code_from_cookie = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
79
122
|
def prune!(hash)
|
80
123
|
hash.delete_if do |_, value|
|
81
124
|
prune!(value) if value.is_a?(Hash)
|
82
125
|
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
83
126
|
end
|
84
127
|
end
|
128
|
+
|
129
|
+
def parse_signed_request(value)
|
130
|
+
signature, encoded_payload = value.split('.')
|
131
|
+
|
132
|
+
decoded_hex_signature = base64_decode_url(signature)#.unpack('H*')
|
133
|
+
decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload))
|
134
|
+
|
135
|
+
unless decoded_payload['algorithm'] == 'HMAC-SHA256'
|
136
|
+
raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}"
|
137
|
+
end
|
138
|
+
|
139
|
+
if valid_signature?(client.secret, decoded_hex_signature, encoded_payload)
|
140
|
+
decoded_payload
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
|
145
|
+
OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
|
146
|
+
end
|
147
|
+
|
148
|
+
def base64_decode_url(value)
|
149
|
+
value += '=' * (4 - value.size.modulo(4))
|
150
|
+
Base64.decode64(value.tr('-_', '+/'))
|
151
|
+
end
|
85
152
|
end
|
86
153
|
end
|
87
154
|
end
|
@@ -1,14 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'omniauth-facebook'
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
3
5
|
|
4
6
|
describe OmniAuth::Strategies::Facebook do
|
5
7
|
before :each do
|
6
8
|
@request = double('Request')
|
7
9
|
@request.stub(:params) { {} }
|
10
|
+
@request.stub(:cookies) { {} }
|
11
|
+
|
12
|
+
@client_id = '123'
|
13
|
+
@client_secret = '53cr3tz'
|
8
14
|
end
|
9
15
|
|
10
16
|
subject do
|
11
|
-
|
17
|
+
args = [@client_id, @client_secret, @options].compact
|
18
|
+
OmniAuth::Strategies::Facebook.new(nil, *args).tap do |strategy|
|
12
19
|
strategy.stub(:request) { @request }
|
13
20
|
end
|
14
21
|
end
|
@@ -29,6 +36,21 @@ describe OmniAuth::Strategies::Facebook do
|
|
29
36
|
end
|
30
37
|
end
|
31
38
|
|
39
|
+
describe '#callback_url' do
|
40
|
+
it "returns value from #authorize_options" do
|
41
|
+
url = 'http://auth.myapp.com/auth/fb/callback'
|
42
|
+
@options = { :authorize_options => { :callback_url => url } }
|
43
|
+
subject.callback_url.should eq(url)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "callback_url from request" do
|
47
|
+
url_base = 'http://auth.request.com'
|
48
|
+
@request.stub(:url) { "#{url_base}/page/path" }
|
49
|
+
subject.stub(:script_name) { "" } # to not depend from Rack env
|
50
|
+
subject.callback_url.should eq("#{url_base}/auth/facebook/callback")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
32
54
|
describe '#authorize_params' do
|
33
55
|
it 'includes default scope for email and offline access' do
|
34
56
|
subject.authorize_params.should be_a(Hash)
|
@@ -256,4 +278,47 @@ describe OmniAuth::Strategies::Facebook do
|
|
256
278
|
subject.extra.should eq({ 'raw_info' => @raw_info })
|
257
279
|
end
|
258
280
|
end
|
281
|
+
|
282
|
+
describe '#signed_request' do
|
283
|
+
context 'cookie not present' do
|
284
|
+
it 'is nil' do
|
285
|
+
subject.send(:signed_request).should be_nil
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context 'cookie present' do
|
290
|
+
before :each do
|
291
|
+
@payload = {
|
292
|
+
'algorithm' => 'HMAC-SHA256',
|
293
|
+
'code' => 'm4c0d3z',
|
294
|
+
'issued_at' => Time.now.to_i,
|
295
|
+
'user_id' => '123456'
|
296
|
+
}
|
297
|
+
|
298
|
+
@request.stub(:cookies) do
|
299
|
+
{ "fbsr_#{@client_id}" => signed_request(@payload, @client_secret) }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'parses the access code out from the cookie' do
|
304
|
+
subject.send(:signed_request).should eq(@payload)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
def signed_request(payload, secret)
|
312
|
+
encoded_payload = base64_encode_url(MultiJson.encode(payload))
|
313
|
+
encoded_signature = base64_encode_url(signature(encoded_payload, secret))
|
314
|
+
[encoded_signature, encoded_payload].join('.')
|
315
|
+
end
|
316
|
+
|
317
|
+
def base64_encode_url(value)
|
318
|
+
Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
|
319
|
+
end
|
320
|
+
|
321
|
+
def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
|
322
|
+
OpenSSL::HMAC.digest(algorithm, secret, payload)
|
323
|
+
end
|
259
324
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-facebook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-12-10 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: omniauth-oauth2
|
16
|
-
requirement: &
|
16
|
+
requirement: &70284008423900 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70284008423900
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70284008422620 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.7.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70284008422620
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &70284008421960 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70284008421960
|
47
47
|
description:
|
48
48
|
email:
|
49
49
|
- mark@mkdynamic.co.uk
|
@@ -80,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
80
|
version: '0'
|
81
81
|
segments:
|
82
82
|
- 0
|
83
|
-
hash: -
|
83
|
+
hash: -3067911989244546828
|
84
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
85
|
none: false
|
86
86
|
requirements:
|
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
89
|
version: '0'
|
90
90
|
segments:
|
91
91
|
- 0
|
92
|
-
hash: -
|
92
|
+
hash: -3067911989244546828
|
93
93
|
requirements: []
|
94
94
|
rubyforge_project:
|
95
95
|
rubygems_version: 1.8.10
|