Skip to content

Commit 147b0ab

Browse files
committed
Fetch key from JWKS URI if available
In non-standard OpenID Connect providers, such as Azure B2C, discovery does not work because the discovery URL does not match the issuer field. If a JWKS URI is provided when discovery is disabled, we should make an HTTP request for the keys and use the response. Closes #72
1 parent 445805b commit 147b0ab

File tree

2 files changed

+55
-13
lines changed

2 files changed

+55
-13
lines changed

lib/omniauth/strategies/openid_connect.rb

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
module OmniAuth
1111
module Strategies
12-
class OpenIDConnect
12+
class OpenIDConnect # rubocop:disable Metrics/ClassLength
1313
include OmniAuth::Strategy
1414
extend Forwardable
1515

@@ -198,9 +198,13 @@ def authorize_uri
198198
end
199199

200200
def public_key
201-
return config.jwks if options.discovery
202-
203-
key_or_secret || config.jwks
201+
@public_key ||= if options.discovery
202+
config.jwks
203+
elsif key_or_secret
204+
key_or_secret
205+
elsif client_options.jwks_uri
206+
fetch_key
207+
end
204208
end
205209

206210
def pkce_authorize_params(verifier)
@@ -213,6 +217,10 @@ def pkce_authorize_params(verifier)
213217

214218
private
215219

220+
def fetch_key
221+
@fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
222+
end
223+
216224
def issuer
217225
resource = "#{ client_options.scheme }://#{ client_options.host }"
218226
resource = "#{ resource }:#{ client_options.port }" if client_options.port
@@ -301,16 +309,17 @@ def session
301309
end
302310

303311
def key_or_secret
304-
case options.client_signing_alg
305-
when :HS256, :HS384, :HS512
306-
client_options.secret
307-
when :RS256, :RS384, :RS512
308-
if options.client_jwk_signing_key
309-
parse_jwk_key(options.client_jwk_signing_key)
310-
elsif options.client_x509_signing_key
311-
parse_x509_key(options.client_x509_signing_key)
312+
@key_or_secret ||=
313+
case options.client_signing_alg&.to_sym
314+
when :HS256, :HS384, :HS512
315+
client_options.secret
316+
when :RS256, :RS384, :RS512
317+
if options.client_jwk_signing_key
318+
parse_jwk_key(options.client_jwk_signing_key)
319+
elsif options.client_x509_signing_key
320+
parse_x509_key(options.client_x509_signing_key)
321+
end
312322
end
313-
end
314323
end
315324

316325
def parse_x509_key(key)

test/lib/omniauth/strategies/openid_connect_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,39 @@ def test_callback_phase_with_invalid_state_without_state_verification
335335
strategy.callback_phase
336336
end
337337

338+
def test_callback_phase_with_jwks_uri
339+
id_token = jwt.to_s
340+
state = SecureRandom.hex(16)
341+
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
342+
request.stubs(:path_info).returns('')
343+
344+
strategy.options.issuer = 'example.com'
345+
strategy.options.client_options.jwks_uri = 'https://jwks.example.com'
346+
strategy.options.response_type = 'id_token'
347+
348+
HTTPClient
349+
.any_instance.stubs(:get_content)
350+
.with(strategy.options.client_options.jwks_uri)
351+
.returns(jwks.to_json)
352+
353+
strategy.unstub(:user_info)
354+
access_token = stub('OpenIDConnect::AccessToken')
355+
access_token.stubs(:access_token)
356+
access_token.stubs(:refresh_token)
357+
access_token.stubs(:expires_in)
358+
access_token.stubs(:scope)
359+
access_token.stubs(:id_token).returns(id_token)
360+
361+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
362+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
363+
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
364+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
365+
id_token.expects(:verify!)
366+
367+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
368+
strategy.callback_phase
369+
end
370+
338371
def test_callback_phase_with_error
339372
state = SecureRandom.hex(16)
340373
request.stubs(:params).returns('error' => 'invalid_request')

0 commit comments

Comments
 (0)