Skip to content

Commit 32618de

Browse files
committed
Merge pull request #18 from AlexanderPavlenko/csrf
Use "state" param to mitigate CSRF
2 parents 7255b71 + 9f9992b commit 32618de

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

lib/omniauth/strategies/oauth2.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'oauth2'
44
require 'omniauth'
55
require 'timeout'
6+
require 'securerandom'
67

78
module OmniAuth
89
module Strategies
@@ -47,7 +48,16 @@ def request_phase
4748
end
4849

4950
def authorize_params
50-
options.authorize_params.merge(options.authorize_options.inject({}){|h,k| h[k.to_sym] = options[k] if options[k]; h})
51+
if options.authorize_params[:state].to_s.empty?
52+
options.authorize_params[:state] = SecureRandom.hex(24)
53+
end
54+
params = options.authorize_params.merge(options.authorize_options.inject({}){|h,k| h[k.to_sym] = options[k] if options[k]; h})
55+
if OmniAuth.config.test_mode
56+
@env ||= {}
57+
@env['rack.session'] ||= {}
58+
end
59+
session['omniauth.state'] = params[:state]
60+
params
5161
end
5262

5363
def token_params
@@ -58,6 +68,9 @@ def callback_phase
5868
if request.params['error'] || request.params['error_reason']
5969
raise CallbackError.new(request.params['error'], request.params['error_description'] || request.params['error_reason'], request.params['error_uri'])
6070
end
71+
if request.params['state'].to_s.empty? || request.params['state'] != session.delete('omniauth.state')
72+
raise CallbackError.new(nil, :csrf_detected)
73+
end
6174

6275
self.access_token = build_access_token
6376
self.access_token = access_token.refresh! if access_token.expired?

spec/omniauth/strategies/oauth2_spec.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
def app; lambda{|env| [200, {}, ["Hello."]]} end
55
let(:fresh_strategy){ Class.new(OmniAuth::Strategies::OAuth2) }
66

7+
before do
8+
OmniAuth.config.test_mode = true
9+
end
10+
11+
after do
12+
OmniAuth.config.test_mode = false
13+
end
14+
715
describe '#client' do
816
subject{ fresh_strategy }
917

@@ -22,13 +30,20 @@ def app; lambda{|env| [200, {}, ["Hello."]]} end
2230
subject { fresh_strategy }
2331

2432
it 'should include any authorize params passed in the :authorize_params option' do
25-
instance = subject.new('abc', 'def', :authorize_params => {:foo => 'bar', :baz => 'zip'})
26-
instance.authorize_params.should == {'foo' => 'bar', 'baz' => 'zip'}
33+
instance = subject.new('abc', 'def', :authorize_params => {:foo => 'bar', :baz => 'zip', :state => '123'})
34+
instance.authorize_params.should == {'foo' => 'bar', 'baz' => 'zip', 'state' => '123'}
2735
end
2836

2937
it 'should include top-level options that are marked as :authorize_options' do
30-
instance = subject.new('abc', 'def', :authorize_options => [:scope, :foo], :scope => 'bar', :foo => 'baz')
31-
instance.authorize_params.should == {'scope' => 'bar', 'foo' => 'baz'}
38+
instance = subject.new('abc', 'def', :authorize_options => [:scope, :foo], :scope => 'bar', :foo => 'baz', :authorize_params => {:state => '123'})
39+
instance.authorize_params.should == {'scope' => 'bar', 'foo' => 'baz', 'state' => '123'}
40+
end
41+
42+
it 'should include random state in the authorize params' do
43+
instance = subject.new('abc', 'def')
44+
instance.authorize_params.keys.should == ['state']
45+
instance.session['omniauth.state'].should_not be_empty
46+
instance.session['omniauth.state'].should == instance.authorize_params['state']
3247
end
3348
end
3449

0 commit comments

Comments
 (0)