Skip to content

Commit 33bc98e

Browse files
authored
Merge pull request synth#37 from jaedonfarrugia/support-common-jwks
Add support for Microsoft common JWKS
2 parents 21f4204 + 68aeca2 commit 33bc98e

File tree

2 files changed

+61
-17
lines changed

2 files changed

+61
-17
lines changed

lib/omniauth/microsoft_graph/domain_verifier.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module MicrosoftGraph
99
# https://www.descope.com/blog/post/noauth
1010
# https://clerk.com/docs/authentication/social-connections/microsoft#stay-secure-against-the-n-o-auth-vulnerability
1111
OIDC_CONFIG_URL = 'https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration'
12+
COMMON_JWKS_URL = 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
1213

1314
class DomainVerificationError < OmniAuth::Error; end
1415

@@ -62,13 +63,25 @@ def verify!
6263
def domain_verified_jwt_claim
6364
oidc_config = access_token.get(OIDC_CONFIG_URL).parsed
6465
algorithms = oidc_config['id_token_signing_alg_values_supported']
65-
keys = JWT::JWK::Set.new(access_token.get(oidc_config['jwks_uri']).parsed)
66-
decoded_token = JWT.decode(id_token, nil, true, algorithms: algorithms, jwks: keys)
66+
jwks = get_jwks(oidc_config)
67+
decoded_token = JWT.decode(id_token, nil, true, algorithms: algorithms, jwks: jwks)
68+
xms_edov_valid?(decoded_token)
69+
rescue JWT::VerificationError, ::OAuth2::Error
70+
false
71+
end
72+
73+
def xms_edov_valid?(decoded_token)
6774
# https://github.com/MicrosoftDocs/azure-docs/issues/111425#issuecomment-1761043378
6875
# Comments seemed to indicate the value is not consistent
6976
['1', 1, 'true', true].include?(decoded_token.first['xms_edov'])
70-
rescue JWT::VerificationError, ::OAuth2::Error
71-
false
77+
end
78+
79+
def get_jwks(oidc_config)
80+
# Depending on the tenant, the JWKS endpoint might be different. We need to
81+
# consider both the JWKS from the OIDC configuration and the common JWKS endpoint.
82+
oidc_config_jwk_keys = access_token.get(oidc_config['jwks_uri']).parsed[:keys]
83+
common_jwk_keys = access_token.get(COMMON_JWKS_URL).parsed[:keys]
84+
JWT::JWK::Set.new(oidc_config_jwk_keys + common_jwk_keys)
7285
end
7386

7487
def verification_error_message

spec/omniauth/microsoft_graph/domain_verifier_spec.rb

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,65 @@
4141
end
4242

4343
context 'when the ID token indicates domain verification' do
44-
# Sign a fake ID token with our own local key
45-
let(:mock_key) do
46-
optional_parameters = { kid: 'mock-kid', use: 'sig', alg: 'RS256' }
44+
let(:mock_oidc_key) do
45+
optional_parameters = { kid: 'mock_oidc_key', use: 'sig', alg: 'RS256' }
4746
JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
4847
end
49-
let(:id_token) do
50-
payload = { email: email, xms_edov: true }
51-
JWT.encode(payload, mock_key.signing_key, mock_key[:alg], kid: mock_key[:kid])
48+
49+
let(:mock_common_key) do
50+
optional_parameters = { kid: 'mock_common_key', use: 'sig', alg: 'RS256' }
51+
JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
5252
end
5353

54-
# Mock the API responses to return the local key
54+
# Mock the API responses to return the mock keys
5555
before do
5656
allow(access_token).to receive(:get)
5757
.with(OmniAuth::MicrosoftGraph::OIDC_CONFIG_URL)
5858
.and_return(
59-
double('OAuth2::Response', parsed: {
60-
'id_token_signing_alg_values_supported' => ['RS256'],
61-
'jwks_uri' => 'https://example.com/jwks-keys'
62-
})
59+
double(
60+
'OAuth2::Response',
61+
parsed: {
62+
'id_token_signing_alg_values_supported' => ['RS256'],
63+
'jwks_uri' => 'https://example.com/jwks-keys',
64+
}
65+
)
6366
)
6467
allow(access_token).to receive(:get)
6568
.with('https://example.com/jwks-keys')
6669
.and_return(
67-
double('OAuth2::Response', parsed: JWT::JWK::Set.new(mock_key).export)
70+
double(
71+
'OAuth2::Response',
72+
parsed: JWT::JWK::Set.new(mock_oidc_key).export
73+
)
74+
)
75+
allow(access_token).to receive(:get)
76+
.with(OmniAuth::MicrosoftGraph::COMMON_JWKS_URL)
77+
.and_return(
78+
double(
79+
'OAuth2::Response',
80+
parsed: JWT::JWK::Set.new(mock_common_key).export,
81+
body: JWT::JWK::Set.new(mock_common_key).export.to_json
82+
)
6883
)
6984
end
7085

71-
it { is_expected.to be_truthy }
86+
context 'when the kid exists in the oidc key' do
87+
let(:id_token) do
88+
payload = { email: email, xms_edov: true }
89+
JWT.encode(payload, mock_oidc_key.signing_key, mock_oidc_key[:alg], kid: mock_oidc_key[:kid])
90+
end
91+
92+
it { is_expected.to be_truthy }
93+
end
94+
95+
context "when the kid exists in the common key" do
96+
let(:id_token) do
97+
payload = { email: email, xms_edov: true }
98+
JWT.encode(payload, mock_common_key.signing_key, mock_common_key[:alg], kid: mock_common_key[:kid])
99+
end
100+
101+
it { is_expected.to be_truthy }
102+
end
72103
end
73104

74105
context 'when all verification strategies fail' do

0 commit comments

Comments
 (0)