From 23d4b13e67b1e99006d4ec7d0813e8fe64303a9f Mon Sep 17 00:00:00 2001 From: Manuel van Rijn Date: Fri, 5 Jul 2024 15:46:43 +0200 Subject: [PATCH] Add `audience` to the `client_options` (#179) * Add `audience` to the `client_options` I've come across an issue where the `identifier` wasn't equal to the `audience` in the token. This resulted in verification errors because currently it will verify the `aud` against the `identifier` if no `audience` is specified. In this PR, I introduced the `audience` as `client_options` and will pass this along in the `verify!` of the `decoded_id_token` so the openid_connect gem [can handle the expected audience](https://github.com/nov/openid_connect/blob/e1eb8ea962af43752b1aed2c1063a3e24f96c5bc/lib/openid_connect/response_object/id_token.rb#L30-L32) * Only pass along `audience` if it is specified * Update README.md Co-authored-by: Roger Meier --------- Co-authored-by: Roger Meier --- README.md | 1 + lib/omniauth/strategies/openid_connect.rb | 12 ++++++++--- .../strategies/openid_connect_test.rb | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 805ee8f5..8bb0a0f6 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ These are the configuration options for the client_options hash of the configura | scheme | The http scheme to use | https | | | host | The host of the authorization server | nil | | | port | The port for the authorization server | 443 | | +| audience | The intended consumer (`aud` field) of the id_token | nil | | | authorization_endpoint | The authorize endpoint on the authorization server | /authorize | yes | | token_endpoint | The token endpoint on the authorization server | /token | yes | | userinfo_endpoint | The user info endpoint on the authorization server | /userinfo | yes | diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index e6143a93..73dd0fe0 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -28,6 +28,7 @@ class OpenIDConnect # rubocop:disable Metrics/ClassLength scheme: 'https', host: nil, port: 443, + audience: nil, authorization_endpoint: '/authorize', token_endpoint: '/token', userinfo_endpoint: '/userinfo', @@ -470,9 +471,14 @@ def configured_response_type def verify_id_token!(id_token) return unless id_token - decode_id_token(id_token).verify!(issuer: options.issuer, - client_id: client_options.identifier, - nonce: params['nonce'].presence || stored_nonce) + verify_kwargs = { + issuer: options.issuer, + client_id: client_options.identifier, + nonce: params['nonce'].presence || stored_nonce, + } + verify_kwargs.merge!(audience: client_options.audience) if client_options.audience + + decode_id_token(id_token).verify!(**verify_kwargs) end class CallbackError < StandardError diff --git a/test/lib/omniauth/strategies/openid_connect_test.rb b/test/lib/omniauth/strategies/openid_connect_test.rb index 6059df27..ffa9f708 100644 --- a/test/lib/omniauth/strategies/openid_connect_test.rb +++ b/test/lib/omniauth/strategies/openid_connect_test.rb @@ -248,6 +248,27 @@ def test_callback_phase_with_id_token strategy.callback_phase end + def test_callback_phase_with_audience + state = SecureRandom.hex(16) + strategy.options.response_type = 'id_token' + strategy.options.issuer = 'example.com' + strategy.options.client_options.audience = 'my_audience' + + id_token = stub('OpenIDConnect::ResponseObject::IdToken') + id_token.expects(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, audience: 'my_audience', + nonce: nonce).returns(true) + id_token.stubs(:raw_attributes, :to_h).returns(payload) + + request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token) + request.stubs(:path).returns('') + + strategy.stubs(:decode_id_token).returns(id_token) + strategy.stubs(:stored_state).returns(state) + + strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce }) + strategy.callback_phase + end + def test_callback_phase_with_id_token_and_param_provided_nonce # rubocop:disable Metrics/AbcSize code = SecureRandom.hex(16) state = SecureRandom.hex(16)