omniauth-facebook 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
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
|