diff --git a/lib/omniauth/strategies/entra_id.rb b/lib/omniauth/strategies/entra_id.rb index 619b3ca..38dd2f0 100644 --- a/lib/omniauth/strategies/entra_id.rb +++ b/lib/omniauth/strategies/entra_id.rb @@ -11,7 +11,8 @@ class EntraId < OmniAuth::Strategies::OAuth2 option :tenant_provider, nil option :jwt_leeway, 60 - DEFAULT_SCOPE = 'openid profile email' + DEFAULT_SCOPE = 'openid profile email' + COMMON_TENANT_ID = 'common' # The tenant_provider must return client_id, client_secret and, # optionally, tenant_id and base_url. @@ -43,7 +44,7 @@ def client options.tenant_id = if provider.respond_to?(:tenant_id) provider.tenant_id else - 'common' + COMMON_TENANT_ID end options.base_url = if provider.respond_to?(:base_url ) @@ -72,14 +73,14 @@ def client 'oauth2/v2.0' end - base_url = if options.custom_policy && options.tenant_name + tenanted_endpoint_base_url = if options.custom_policy && options.tenant_name "https://#{options.tenant_name}.b2clogin.com/#{options.tenant_name}.onmicrosoft.com/#{options.custom_policy}" else "#{options.base_url}/#{options.tenant_id}" end - options.client_options.authorize_url = "#{base_url}/#{oauth2}/authorize" - options.client_options.token_url = "#{base_url}/#{oauth2}/token" + options.client_options.authorize_url = "#{tenanted_endpoint_base_url}/#{oauth2}/authorize" + options.client_options.token_url = "#{tenanted_endpoint_base_url}/#{oauth2}/token" super end @@ -136,7 +137,11 @@ def raw_info # sense to verify the token issuer, because the value of 'iss' in the # token depends on the 'tid' in the token itself. # - issuer = options.tenant_id.nil? ? nil : "#{options.base_url}/#{options.tenant_id}/v2.0" + issuer = if options.tenant_id.nil? || options.tenant_id == COMMON_TENANT_ID + nil + else + "#{options.base_url || BASE_URL}/#{options.tenant_id}/v2.0" + end # https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens#validate-tokens # diff --git a/spec/omniauth/strategies/entra_id_spec.rb b/spec/omniauth/strategies/entra_id_spec.rb index 4057e19..b027c43 100644 --- a/spec/omniauth/strategies/entra_id_spec.rb +++ b/spec/omniauth/strategies/entra_id_spec.rb @@ -467,7 +467,7 @@ def adfs? end # "describe '#client' do" end # "describe 'dynamic configuration with on premise ADFS' do" - describe 'raw_info' do + describe 'raw_info and validation' do let(:issued_at ) { Time.now.utc.to_i } let(:expires_at) { (Time.now.utc + 3600).to_i } @@ -476,7 +476,6 @@ def adfs? end let(:id_token_info) do - { ver: '2.0', iss: 'https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0', @@ -603,15 +602,55 @@ def adfs? end end # "context 'with an invalid audience' do" - context 'with an invalid issuer' do - subject do - OmniAuth::Strategies::EntraId.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: 'test-tenant'}) - end + context 'issuers' do + context 'when valid' do + subject do + OmniAuth::Strategies::EntraId.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: '9188040d-6c67-4c5b-b112-36a304b66dad'}) + end - it 'fails validation' do - expect { subject.info }.to raise_error(JWT::InvalidIssuerError) - end - end # "context 'with an invalid issuer' do" + it 'passes validation' do + expect { subject.info }.to_not raise_error() + end + end # "context 'when valid' do" + + context 'when invalid' do + subject do + OmniAuth::Strategies::EntraId.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: 'a-mismatched-tenant-id'}) + end + + it 'fails validation' do + expect { subject.info }.to raise_error(JWT::InvalidIssuerError) + end + end # "context 'when invalid' do" + + context 'multi-tenant' do + let(:id_token_info) do + hash = super() + hash['iss'] = 'invalid issuer that should be ignored' + hash + end + + context 'no tenant specified' do + subject do + OmniAuth::Strategies::EntraId.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: nil}) + end + + it 'skips issuer validation since tenant ID is unknown' do + expect { subject.info }.to_not raise_error() + end + end # "context 'no tenant specified' do" + + context '"common" tenant specified' do + subject do + OmniAuth::Strategies::EntraId.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: OmniAuth::Strategies::EntraId::COMMON_TENANT_ID}) + end + + it 'skips issuer validation since tenant ID is unknown' do + expect { subject.info }.to_not raise_error() + end + end # "context '"common" tenant specified' do" + end # "context 'multi-tenant' do" + end # "context 'issuers' do" context 'with an invalid not_before' do let(:issued_at) { (Time.now.utc + 70).to_i } # Invalid because leeway is 60 seconds @@ -667,7 +706,7 @@ def adfs? expect { subject.info }.to raise_error(JWT::ExpiredSignature) end end # "context 'with an expired token' do" - end # "describe 'raw_info' do" + end # "describe 'raw_info and validation' do" describe 'callback_url' do subject do