Skip to content

Commit 8d1f8ed

Browse files
authored
fix: make require_state skip verification of state (#181)
In #127, `require_state` was introduced because according to https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.1, `state` is recommended but not required: ``` state RECOMMENDED. Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie. ``` During review, the `require_state` parameter was modified to verify `state` as long as `stored_state` was present. However, `stored_state` always holds at least a random value, so when `require_state` were `false` and if an OpenID provider did not relay the `state` value, authentication would halt with a "Invalid 'state' parameter" error. This commit updates it so that if `require_state` is set to `false`, the `state` parameter is never checked at all.
1 parent a6a2146 commit 8d1f8ed

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

lib/omniauth/strategies/openid_connect.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def request_phase
120120
def callback_phase
121121
error = params['error_reason'] || params['error']
122122
error_description = params['error_description'] || params['error_reason']
123-
invalid_state = (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state
123+
invalid_state = options.require_state && (params['state'].to_s.empty? || params['state'] != stored_state)
124124

125125
raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error
126126
raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state

test/lib/omniauth/strategies/openid_connect_test.rb

+32-2
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def test_callback_phase_with_no_state_without_state_verification # rubocop:disab
496496
strategy.callback_phase
497497
end
498498

499-
def test_callback_phase_with_invalid_state_without_state_verification
499+
def test_callback_phase_with_invalid_state_without_state_verification # rubocop:disable Metrics/AbcSize
500500
code = SecureRandom.hex(16)
501501
state = SecureRandom.hex(16)
502502

@@ -505,8 +505,38 @@ def test_callback_phase_with_invalid_state_without_state_verification
505505
request.stubs(:params).returns('code' => code, 'state' => 'foobar')
506506
request.stubs(:path).returns('')
507507

508+
strategy.options.client_options.host = 'example.com'
509+
strategy.options.discovery = true
510+
511+
issuer = stub('OpenIDConnect::Discovery::Issuer')
512+
issuer.stubs(:issuer).returns('https://example.com/')
513+
::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
514+
515+
config = stub('OpenIDConnect::Discovery::Provder::Config')
516+
config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
517+
config.stubs(:token_endpoint).returns('https://example.com/token')
518+
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
519+
config.stubs(:jwks_uri).returns('https://example.com/jwks')
520+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
521+
522+
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
523+
524+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
525+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
526+
id_token.stubs(:verify!).with(issuer: 'https://example.com/', client_id: @identifier, nonce: nonce).returns(true)
527+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
528+
529+
strategy.unstub(:user_info)
530+
access_token = stub('OpenIDConnect::AccessToken')
531+
access_token.stubs(:access_token)
532+
access_token.stubs(:refresh_token)
533+
access_token.stubs(:expires_in)
534+
access_token.stubs(:scope)
535+
access_token.stubs(:id_token).returns(jwt.to_s)
536+
client.expects(:access_token!).at_least_once.returns(access_token)
537+
access_token.expects(:userinfo!).returns(user_info)
538+
508539
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
509-
strategy.expects(:fail!)
510540
strategy.callback_phase
511541
end
512542

0 commit comments

Comments
 (0)