diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c867ea9..9e90030b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,6 @@ jobs: steps: - checkout - run: gem install bundler:2.3.22 - - run: rm Gemfile.lock - restore_cache: key: gems-v2-{{ checksum "Gemfile.lock" }} - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 2b08ca51..18c1b425 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/ruby/.devcontainer/base.Dockerfile # [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster -ARG VARIANT="3.1-bullseye" +ARG VARIANT="3.2-bullseye" FROM mcr.microsoft.com/vscode/devcontainers/ruby:0-${VARIANT} # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cce24e4..fe9112d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [v5.14.1](https://github.com/auth0/ruby-auth0/tree/v5.14.1) (2023-07-19) +[Full Changelog](https://github.com/auth0/ruby-auth0/compare/v5.14.0...v5.14.1) + +**Fixed** +- chore: should not lowercase org_name claim [\#499](https://github.com/auth0/ruby-auth0/pull/499) ([stevehobbsdev](https://github.com/stevehobbsdev)) + +## [v5.14.0](https://github.com/auth0/ruby-auth0/tree/v5.14.0) (2023-07-13) +[Full Changelog](https://github.com/auth0/ruby-auth0/compare/v5.13.0...v5.14.0) + +**Added** +- [SDK-4386] Support Organization Name in Authorize [\#495](https://github.com/auth0/ruby-auth0/pull/495) ([stevehobbsdev](https://github.com/stevehobbsdev)) + ## [v5.13.0](https://github.com/auth0/ruby-auth0/tree/v5.13.0) (2023-04-24) [Full Changelog](https://github.com/auth0/ruby-auth0/compare/v5.12.0...v5.13.0) diff --git a/EXAMPLES.md b/EXAMPLES.md index 73732b33..fe5f2fd9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -85,7 +85,7 @@ Note that Organizations is currently only available to customers on our Enterpri ### Logging in with an Organization -Configure the Authentication API client and pass your Organization ID to the authorize url: +Configure the Authentication API client and pass your Organization ID or name to the authorize url: ```ruby require 'auth0' @@ -94,7 +94,7 @@ require 'auth0' client_id: '{YOUR_APPLICATION_CLIENT_ID}', client_secret: '{YOUR_APPLICATION_CLIENT_SECRET}', domain: '{YOUR_TENANT}.auth0.com', - organization: "{YOUR_ORGANIZATION_ID}" + organization: "{YOUR_ORGANIZATION_ID_OR_NAME}" ) universal_login_url = @auth0_client.authorization_url("https://{YOUR_APPLICATION_CALLBACK_URL}") @@ -113,7 +113,7 @@ require 'auth0' client_id: '{YOUR_APPLICATION_CLIENT_ID}', client_secret: '{YOUR_APPLICATION_CLIENT_ID}', domain: '{YOUR_TENANT}.auth0.com', - organization: "{YOUR_ORGANIZATION_ID}" + organization: "{YOUR_ORGANIZATION_ID_OR_NAME}" ) universal_login_url = @auth0_client.authorization_url("https://{YOUR_APPLICATION_CALLBACK_URL}", { @@ -148,7 +148,7 @@ The method takes the following optional keyword parameters: | `max_age` | Integer | The `max_age` value you sent in the call to `/authorize`, if any. | `nil` | | `issuer` | String | By default the `iss` claim will be checked against the URL of your **Auth0 Domain**. Use this parameter to override that. | `nil` | | `audience` | String | By default the `aud` claim will be compared to your **Auth0 Client ID**. Use this parameter to override that. | `nil` | -| `organization` | String | By default the `org_id` claim will be compared to your **Organization ID**. Use this parameter to override that. | `nil` | +| `organization` | String | By default the `org_id` or `org_name` claims will be compared to the `organization` value specified at client creation. Use this parameter to override that. | `nil` | You can check the signing algorithm value under **Advanced Settings > OAuth > JsonWebToken Signature Algorithm** in your Auth0 application settings panel. [We recommend](https://auth0.com/docs/tokens/concepts/signing-algorithms#our-recommendation) that you make use of asymmetric signing algorithms like `RS256` instead of symmetric ones like `HS256`. @@ -170,15 +170,17 @@ rescue Auth0::InvalidIdToken => e end ``` -### Organization ID Token Validation +### Organization claim validation -If an org_id claim is present in the Access Token, then the claim should be validated by the API to ensure that the value received is expected or known. +If an `org_id` or `org_name` claim is present in the access token, then the claim should be validated by the API to ensure that the value received is expected or known. In particular: - The issuer (iss) claim should be checked to ensure the token was issued by Auth0 -- the org_id claim should be checked to ensure it is a value that is already known to the application. This could be validated against a known list of organization IDs, or perhaps checked in conjunction with the current request URL. e.g. the sub-domain may hint at what organization should be used to validate the Access Token. +- the `org_id` or `org_name` claim should be checked to ensure it is a value that is already known to the application. Which claim you check depends on the organization value being validated: if it starts with `org_`, validate against the `org_id` claim. Otherwise, validate against `org_name`. Further, the value of the `org_name` claim will always be lowercase. To aid the developer experience, you may also lowercase the input organization name when checking against the `org_name`, but do not modify the `org_name` claim value. + +This could be validated against a known list of organization IDs or names, or perhaps checked in conjunction with the current request URL. e.g. the sub-domain may hint at what organization should be used to validate the Access Token. Normally, validating the issuer would be enough to ensure that the token was issued by Auth0. In the case of organizations, additional checks should be made so that the organization within an Auth0 tenant is expected. @@ -186,7 +188,7 @@ If the claim cannot be validated, then the application should deem the token inv ```ruby begin - @auth0_client.validate_id_token 'YOUR_ID_TOKEN', organization: '{Expected org_id}' + @auth0_client.validate_id_token 'YOUR_ID_TOKEN', organization: '{Expected org_id or org_name}' rescue Auth0::InvalidIdToken => e # In this case the ID Token contents should not be trusted end diff --git a/Gemfile.lock b/Gemfile.lock index 4d90c374..9a131a86 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ PATH remote: . specs: - auth0 (5.13.0) + auth0 (5.14.1) addressable (~> 2.8) - jwt (~> 2.5) + jwt (~> 2.7) rest-client (~> 2.1) retryable (~> 3.0) zache (~> 0.12) @@ -11,27 +11,28 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (7.0.4.3) - actionview (= 7.0.4.3) - activesupport (= 7.0.4.3) - rack (~> 2.0, >= 2.2.0) + actionpack (7.0.7.2) + actionview (= 7.0.7.2) + activesupport (= 7.0.7.2) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (7.0.4.3) - activesupport (= 7.0.4.3) + actionview (7.0.7.2) + activesupport (= 7.0.7.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activesupport (7.0.4.3) + activesupport (7.0.7.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) + base64 (0.1.1) builder (3.2.4) coderay (1.1.3) concurrent-ruby (1.2.2) @@ -82,32 +83,30 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) io-console (0.6.0) - irb (1.7.0) - reline (>= 0.3.0) + irb (1.8.1) + rdoc + reline (>= 0.3.8) json (2.6.3) jwt (2.7.1) + language_server-protocol (3.17.0.3) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.20.0) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - lumberjack (1.2.8) + nokogiri (>= 1.12.0) + lumberjack (1.2.9) method_source (1.0.0) mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.0218.1) - minitest (5.18.1) + minitest (5.19.0) multi_json (1.15.0) nenv (0.3.0) netrc (0.11.0) - nokogiri (1.14.3-aarch64-linux) + nokogiri (1.15.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.3-arm64-darwin) - racc (~> 1.4) - nokogiri (1.14.3-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.3-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -122,19 +121,23 @@ GEM pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.1) + psych (5.1.0) + stringio + public_suffix (5.0.3) racc (1.7.1) - rack (2.2.7) + rack (2.2.8) rack-test (0.8.3) rack (>= 1.0, < 3) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.1.1) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.3) - actionpack (= 7.0.4.3) - activesupport (= 7.0.4.3) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.7.2) + actionpack (= 7.0.7.2) + activesupport (= 7.0.7.2) method_source rake (>= 12.2) thor (~> 1.0) @@ -144,8 +147,10 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) + rdoc (6.5.0) + psych (>= 4.0.0) regexp_parser (2.8.1) - reline (0.3.5) + reline (0.3.8) io-console (~> 0.5) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) @@ -153,7 +158,7 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retryable (3.0.5) - rexml (3.2.5) + rexml (3.2.6) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -163,18 +168,20 @@ GEM rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-mocks (3.12.5) + rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-support (3.12.0) - rubocop (1.52.1) + rspec-support (3.12.1) + rubocop (1.56.2) + base64 (~> 0.1.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.0) @@ -194,12 +201,13 @@ GEM simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) + stringio (3.0.8) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) terminal-notifier-guard (1.7.0) - thor (1.2.1) - timecop (0.9.6) + thor (1.2.2) + timecop (0.9.8) tins (1.32.1) sync tzinfo (2.0.6) @@ -208,13 +216,13 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (2.4.2) - vcr (6.1.0) - webmock (3.18.1) + vcr (6.2.0) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - zache (0.13.0) - zeitwerk (2.6.7) + zache (0.13.1) + zeitwerk (2.6.11) PLATFORMS aarch64-linux diff --git a/auth0.gemspec b/auth0.gemspec index 1b7f93da..d1f1e6ab 100644 --- a/auth0.gemspec +++ b/auth0.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.require_paths = ['lib'] s.add_runtime_dependency 'rest-client', '~> 2.1' - s.add_runtime_dependency 'jwt', '~> 2.5' + s.add_runtime_dependency 'jwt', '~> 2.7' s.add_runtime_dependency 'zache', '~> 0.12' s.add_runtime_dependency 'addressable', '~> 2.8' s.add_runtime_dependency 'retryable', '~> 3.0' diff --git a/examples/ruby-api/Gemfile.lock b/examples/ruby-api/Gemfile.lock index 801755a2..8569ce58 100644 --- a/examples/ruby-api/Gemfile.lock +++ b/examples/ruby-api/Gemfile.lock @@ -5,8 +5,8 @@ GEM jwt (2.5.0) mustermann (2.0.2) ruby2_keywords (~> 0.0.1) - nio4r (2.5.8) - puma (5.6.5) + nio4r (2.5.9) + puma (5.6.7) nio4r (~> 2.0) rack (2.2.6.4) rack-protection (2.2.3) diff --git a/lib/auth0/mixins/validation.rb b/lib/auth0/mixins/validation.rb index 2bd895f4..f2596668 100644 --- a/lib/auth0/mixins/validation.rb +++ b/lib/auth0/mixins/validation.rb @@ -188,13 +188,26 @@ def validate_nonce(claims, expected) end def validate_org(claims, expected) - unless claims.key?('org_id') && claims['org_id'].is_a?(String) - raise Auth0::InvalidIdToken, 'Organization Id (org_id) claim must be a string present in the ID token' - end + validate_as_id = expected.start_with? 'org_' + + if validate_as_id + unless claims.key?('org_id') && claims['org_id'].is_a?(String) + raise Auth0::InvalidIdToken, 'Organization Id (org_id) claim must be a string present in the ID token' + end - unless expected == claims['org_id'] - raise Auth0::InvalidIdToken, "Organization Id (org_id) claim value mismatch in the ID token; expected \"#{expected}\","\ - " found \"#{claims['org_id']}\"" + unless expected == claims['org_id'] + raise Auth0::InvalidIdToken, "Organization Id (org_id) claim value mismatch in the ID token; expected \"#{expected}\","\ + " found \"#{claims['org_id']}\"" + end + else + unless claims.key?('org_name') && claims['org_name'].is_a?(String) + raise Auth0::InvalidIdToken, 'Organization Name (org_name) claim must be a string present in the ID token' + end + + unless expected.downcase == claims['org_name'] + raise Auth0::InvalidIdToken, "Organization Name (org_name) claim value mismatch in the ID token; expected \"#{expected}\","\ + " found \"#{claims['org_name']}\"" + end end end diff --git a/lib/auth0/version.rb b/lib/auth0/version.rb index e46ed870..38f8f95b 100644 --- a/lib/auth0/version.rb +++ b/lib/auth0/version.rb @@ -1,4 +1,4 @@ # current version of gem module Auth0 - VERSION = '5.13.0'.freeze + VERSION = '5.14.1'.freeze end diff --git a/spec/lib/auth0/mixins/validation_spec.rb b/spec/lib/auth0/mixins/validation_spec.rb index 56dea958..5d31a5a5 100644 --- a/spec/lib/auth0/mixins/validation_spec.rb +++ b/spec/lib/auth0/mixins/validation_spec.rb @@ -1,5 +1,6 @@ # rubocop:disable Metrics/BlockLength require 'spec_helper' +require 'jwt' RSA_PUB_KEY_JWK_1 = { 'kty': "RSA", 'use': 'sig', 'n': "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-spi5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVsWXI9C-yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT69s7of9-I9l5lsJ9cozf1rxrXX4V1u_SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8AziMCxS-VrRPDM-zfvpIJg3JljAh3PJHDiLu902v9w-Iplu1WyoB2aPfitxEhRN0Yw", 'e': 'AQAB', 'kid': 'test-key-1' }.freeze RSA_PUB_KEY_JWK_2 = { 'kty': "RSA", 'use': 'sig', 'n': "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-spi5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVsWXI9C-yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT69s7of9-I9l5lsJ9cozf1rxrXX4V1u_SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8AziMCxS-VrRPDM-zfvpIJg3JljAh3PJHDiLu902v9w-Iplu1WyoB2aPfitxEhRN0Yw", 'e': 'AQAB', 'kid': 'test-key-2' }.freeze @@ -13,8 +14,14 @@ CLOCK = 1587592561 # Apr 22 2020 21:56:01 UTC CONTEXT = { algorithm: Auth0::Algorithm::HS256.secret(HMAC_SHARED_SECRET), leeway: LEEWAY, audience: 'tokens-test-123', issuer: 'https://tokens-test.auth0.com/', clock: CLOCK }.freeze +def build_id_token(payload = {}) + default_payload = { iss: CONTEXT[:issuer], sub: 'user123', aud: CONTEXT[:audience], exp: CLOCK, iat: CLOCK } + JWT.encode(default_payload.merge(payload), HMAC_SHARED_SECRET, 'HS256') +end + describe Auth0::Mixins::Validation::IdTokenValidator do subject { @instance } + let (:minimal_id_token) { build_id_token } context 'instance' do it 'is expected respond to :validate' do @@ -285,30 +292,73 @@ expect { instance.validate(token) }.to raise_exception("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time \"#{clock}\" is after last auth at \"#{auth_time}\"") end - it 'is expected not to raise an error when org_id exsist in the token, but not required' do - token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNjE2NjE3ODgxLCJpYXQiOjE2MTY0NDUwODEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTYxNjUzMTQ4MSwib3JnX2lkIjoidGVzdE9yZyJ9.AOafUKUNgaxUXpSRYFCeJERcwrQZ4q2NZlutwGXnh9I' - expect { @instance.validate(token) }.not_to raise_exception - end + context 'Organization claims validation' do + it 'is expected not to raise an error when org_id exsist in the token, but not required' do + token = build_id_token org_id: 'org_123' + expect { @instance.validate(token) }.not_to raise_exception + end - it 'is expected to raise an error with a missing but required organization' do - token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNjE2NjE4MTg1LCJpYXQiOjE2MTY0NDUzODUsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTYxNjUzMTc4NX0.UMo5pmgceXO9lIKzbk7X0ZhE5DOe0IP2LfMKdUj03zQ' - instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'a1b2c3d4e5' })) + it 'is expected not to raise an error when org_name exists in the token, but not required' do + token = build_id_token org_name: 'my-organization' + expect { @instance.validate(token) }.not_to raise_exception + end - expect { instance.validate(token) }.to raise_exception('Organization Id (org_id) claim must be a string present in the ID token') - end + it 'is expected to raise an error with a missing but required organization ID' do + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'org_1234' })) + expect { instance.validate(minimal_id_token) }.to raise_exception('Organization Id (org_id) claim must be a string present in the ID token') + end - it 'is expected to raise an error with an invalid organization' do - token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNjE2NjE3ODgxLCJpYXQiOjE2MTY0NDUwODEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTYxNjUzMTQ4MSwib3JnX2lkIjoidGVzdE9yZyJ9.AOafUKUNgaxUXpSRYFCeJERcwrQZ4q2NZlutwGXnh9I' - instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'a1b2c3d4e5' })) + it 'is expected to raise an error with a missing but required organization name' do + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'my-organization' })) + expect { instance.validate(minimal_id_token) }.to raise_exception('Organization Name (org_name) claim must be a string present in the ID token') + end - expect { instance.validate(token) }.to raise_exception('Organization Id (org_id) claim value mismatch in the ID token; expected "a1b2c3d4e5", found "testOrg"') - end + it 'is expected to raise an error with an invalid organization ID' do + token = build_id_token org_id: 'org_1234' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'org_5678' })) + + expect { instance.validate(token) }.to raise_exception('Organization Id (org_id) claim value mismatch in the ID token; expected "org_5678", found "org_1234"') + end + + it 'is expected to raise an error with an invalid organization name' do + token = build_id_token org_name: 'another-organization' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'my-organization' })) + + expect { instance.validate(token) }.to raise_exception('Organization Name (org_name) claim value mismatch in the ID token; expected "my-organization", found "another-organization"') + end + + it 'is expected to NOT raise an error with a valid organization ID' do + token = build_id_token org_id: 'org_1234' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'org_1234' })) + + expect { instance.validate(token) }.not_to raise_exception + end + + it 'is expected to NOT raise an error with a valid organization name' do + token = build_id_token org_name: 'my-organization' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'my-organization' })) + + expect { instance.validate(token) }.not_to raise_exception + end + + it 'is expected to NOT raise an error with organization name in different casing' do + token = build_id_token org_name: 'my-organization' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'MY-ORGANIZATION' })) + + expect { instance.validate(token) }.not_to raise_exception + end - it 'is expected to NOT raise an error with a valid organization' do - token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNjE2NjE3ODgxLCJpYXQiOjE2MTY0NDUwODEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTYxNjUzMTQ4MSwib3JnX2lkIjoidGVzdE9yZyJ9.AOafUKUNgaxUXpSRYFCeJERcwrQZ4q2NZlutwGXnh9I' - instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'testOrg' })) + it 'validates org_id when both claims are present in the token' do + token = build_id_token org_name: 'my-organization', org_id: 'org_1234' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'org_1234' })) + expect { instance.validate(token) }.not_to raise_exception + end - expect { instance.validate(token) }.not_to raise_exception + it 'validates org_name when both claims are present in the token' do + token = build_id_token org_name: 'my-organization', org_id: 'org_1234' + instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ organization: 'my-organization' })) + expect { instance.validate(token) }.not_to raise_exception + end end end end