Skip to content

Commit

Permalink
Fetch key from JWKS URI if available
Browse files Browse the repository at this point in the history
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
  • Loading branch information
stanhu committed Nov 30, 2022
1 parent 445805b commit 147b0ab
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 13 deletions.
35 changes: 22 additions & 13 deletions lib/omniauth/strategies/openid_connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

module OmniAuth
module Strategies
class OpenIDConnect
class OpenIDConnect # rubocop:disable Metrics/ClassLength
include OmniAuth::Strategy
extend Forwardable

Expand Down Expand Up @@ -198,9 +198,13 @@ def authorize_uri
end

def public_key
return config.jwks if options.discovery

key_or_secret || config.jwks
@public_key ||= if options.discovery
config.jwks
elsif key_or_secret
key_or_secret
elsif client_options.jwks_uri
fetch_key
end
end

def pkce_authorize_params(verifier)
Expand All @@ -213,6 +217,10 @@ def pkce_authorize_params(verifier)

private

def fetch_key
@fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
end

def issuer
resource = "#{ client_options.scheme }://#{ client_options.host }"
resource = "#{ resource }:#{ client_options.port }" if client_options.port
Expand Down Expand Up @@ -301,16 +309,17 @@ def session
end

def key_or_secret
case options.client_signing_alg
when :HS256, :HS384, :HS512
client_options.secret
when :RS256, :RS384, :RS512
if options.client_jwk_signing_key
parse_jwk_key(options.client_jwk_signing_key)
elsif options.client_x509_signing_key
parse_x509_key(options.client_x509_signing_key)
@key_or_secret ||=
case options.client_signing_alg&.to_sym
when :HS256, :HS384, :HS512
client_options.secret
when :RS256, :RS384, :RS512
if options.client_jwk_signing_key
parse_jwk_key(options.client_jwk_signing_key)
elsif options.client_x509_signing_key
parse_x509_key(options.client_x509_signing_key)
end
end
end
end

def parse_x509_key(key)
Expand Down
33 changes: 33 additions & 0 deletions test/lib/omniauth/strategies/openid_connect_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,39 @@ def test_callback_phase_with_invalid_state_without_state_verification
strategy.callback_phase
end

def test_callback_phase_with_jwks_uri
id_token = jwt.to_s
state = SecureRandom.hex(16)
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
request.stubs(:path_info).returns('')

strategy.options.issuer = 'example.com'
strategy.options.client_options.jwks_uri = 'https://jwks.example.com'
strategy.options.response_type = 'id_token'

HTTPClient
.any_instance.stubs(:get_content)
.with(strategy.options.client_options.jwks_uri)
.returns(jwks.to_json)

strategy.unstub(:user_info)
access_token = stub('OpenIDConnect::AccessToken')
access_token.stubs(:access_token)
access_token.stubs(:refresh_token)
access_token.stubs(:expires_in)
access_token.stubs(:scope)
access_token.stubs(:id_token).returns(id_token)

id_token = stub('OpenIDConnect::ResponseObject::IdToken')
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
id_token.expects(:verify!)

strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
strategy.callback_phase
end

def test_callback_phase_with_error
state = SecureRandom.hex(16)
request.stubs(:params).returns('error' => 'invalid_request')
Expand Down

0 comments on commit 147b0ab

Please sign in to comment.