omniauth-auth0 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +1 -1
- data/.github/CODEOWNERS +1 -1
- data/.github/ISSUE_TEMPLATE/Bug Report.yml +76 -0
- data/.github/ISSUE_TEMPLATE/Feature Request.yml +53 -0
- data/.github/ISSUE_TEMPLATE/config.yml +2 -2
- data/.github/actions/get-prerelease/action.yml +30 -0
- data/.github/actions/get-release-notes/action.yml +42 -0
- data/.github/actions/get-version/action.yml +21 -0
- data/.github/actions/release-create/action.yml +47 -0
- data/.github/actions/rl-scanner/action.yml +71 -0
- data/.github/actions/rubygems-publish/action.yml +30 -0
- data/.github/actions/setup/action.yml +28 -0
- data/.github/actions/tag-exists/action.yml +36 -0
- data/.github/dependabot.yml +13 -0
- data/.github/workflows/codeql.yml +53 -0
- data/.github/workflows/matrix.json +7 -0
- data/.github/workflows/publish.yml +33 -0
- data/.github/workflows/rl-scanner.yml +65 -0
- data/.github/workflows/ruby-release.yml +72 -0
- data/.github/workflows/snyk.yml +40 -0
- data/.github/workflows/test.yml +69 -0
- data/.shiprc +2 -1
- data/.version +1 -0
- data/CHANGELOG.md +20 -0
- data/EXAMPLES.md +19 -5
- data/Gemfile +4 -5
- data/Gemfile.lock +128 -91
- data/README.md +42 -1
- data/lib/omniauth/auth0/jwt_token.rb +38 -0
- data/lib/omniauth/auth0/jwt_validator.rb +19 -3
- data/lib/omniauth/strategies/auth0.rb +48 -14
- data/lib/omniauth-auth0/version.rb +1 -1
- data/omniauth-auth0.gemspec +1 -0
- data/spec/omniauth/auth0/jwt_token_spec.rb +87 -0
- data/spec/omniauth/auth0/jwt_validator_spec.rb +109 -31
- data/spec/omniauth/strategies/auth0_spec.rb +478 -230
- data/spec/spec_helper.rb +1 -0
- metadata +39 -14
- data/.circleci/config.yml +0 -63
- data/.gemrelease +0 -2
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -39
- data/.github/ISSUE_TEMPLATE/report_a_bug.md +0 -55
- data/.github/workflows/semgrep.yml +0 -24
data/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://codecov.io/gh/auth0/omniauth-auth0)
|
|
6
6
|
[](https://badge.fury.io/rb/omniauth-auth0)
|
|
7
7
|
[](https://github.com/auth0/omniauth-auth0/blob/master/LICENSE)
|
|
8
|
+
[](https://deepwiki.com/auth0/omniauth-auth0)
|
|
8
9
|
|
|
9
10
|
<div>
|
|
10
11
|
📚 <a href="#documentation">Documentation</a> - 🚀 <a href="#getting-started">Getting started</a> - 💻 <a href="https://www.rubydoc.info/gems/omniauth-auth0">API reference</a> - 💬 <a href="#feedback">Feedback</a>
|
|
@@ -53,6 +54,8 @@ Adding the SDK to your Rails app requires a few steps:
|
|
|
53
54
|
|
|
54
55
|
Create the file `./config/auth0.yml` within your application directory with the following content:
|
|
55
56
|
|
|
57
|
+
### For client secret authentication
|
|
58
|
+
|
|
56
59
|
```yml
|
|
57
60
|
development:
|
|
58
61
|
auth0_domain: <YOUR_DOMAIN>
|
|
@@ -60,10 +63,25 @@ development:
|
|
|
60
63
|
auth0_client_secret: <YOUR AUTH0 CLIENT SECRET>
|
|
61
64
|
```
|
|
62
65
|
|
|
66
|
+
#### For client assertion signing key authentication
|
|
67
|
+
|
|
68
|
+
```yml
|
|
69
|
+
development:
|
|
70
|
+
auth0_domain: <YOUR_DOMAIN>
|
|
71
|
+
auth0_client_id: <YOUR_CLIENT_ID>
|
|
72
|
+
auth0_client_assertion_signing_key: <YOUR AUTH0 CLIENT ASSERTION SIGNING PRIVATE KEY>
|
|
73
|
+
auth0_client_assertion_signing_algorithm: <YOUR AUTH0 CLIENT ASSERTION SIGNING ALGORITHM>
|
|
74
|
+
```
|
|
75
|
+
**Note**: you must upload the corresponding public key to your Auth0 tenant, so that Auth0 is able to verify the JWT signature.
|
|
76
|
+
|
|
77
|
+
client_assertion_signing_algorithm is optional and defaults to RS256.
|
|
78
|
+
|
|
63
79
|
### Create the initializer
|
|
64
80
|
|
|
65
81
|
Create a new Ruby file in `./config/initializers/auth0.rb` to configure the OmniAuth middleware:
|
|
66
82
|
|
|
83
|
+
### For client secret authentication
|
|
84
|
+
|
|
67
85
|
```ruby
|
|
68
86
|
AUTH0_CONFIG = Rails.application.config_for(:auth0)
|
|
69
87
|
|
|
@@ -81,6 +99,29 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
|
81
99
|
end
|
|
82
100
|
```
|
|
83
101
|
|
|
102
|
+
#### For client assertion signing key authentication
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
AUTH0_CONFIG = Rails.application.config_for(:auth0)
|
|
106
|
+
|
|
107
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
108
|
+
provider(
|
|
109
|
+
:auth0,
|
|
110
|
+
AUTH0_CONFIG['auth0_client_id'],
|
|
111
|
+
nil,
|
|
112
|
+
AUTH0_CONFIG['auth0_domain'],
|
|
113
|
+
callback_path: '/auth/auth0/callback',
|
|
114
|
+
authorize_params: {
|
|
115
|
+
scope: 'openid profile'
|
|
116
|
+
},
|
|
117
|
+
client_assertion_signing_key: OpenSSL::PKey::RSA.new(AUTH0_CONFIG[:auth0_client_assertion_signing_key]),
|
|
118
|
+
client_assertion_signing_algorithm: AUTH0_CONFIG[:auth0_client_assertion_signing_algorithm]
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Note**: The client_assertion_signing_key must be provided as a PKey object.
|
|
124
|
+
|
|
84
125
|
### Create the callback controller
|
|
85
126
|
|
|
86
127
|
Create a new controller `./app/controllers/auth0_controller.rb` to handle the callback from Auth0.
|
|
@@ -165,4 +206,4 @@ Please do not report security vulnerabilities on the public GitHub issue tracker
|
|
|
165
206
|
</p>
|
|
166
207
|
<p align="center">
|
|
167
208
|
This project is licensed under the MIT license. See the <a href="https://github.com/auth0/omniauth-auth0/blob/master/LICENSE"> LICENSE</a> file for more info.
|
|
168
|
-
</p>
|
|
209
|
+
</p>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jwt'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
module OmniAuth
|
|
7
|
+
module Auth0
|
|
8
|
+
# JWTToken class to generate a JWT token for client assertion
|
|
9
|
+
# as per the OAuth 2.0 Client Credentials Grant specification.
|
|
10
|
+
class JWTToken
|
|
11
|
+
attr_reader :client_id, :domain_url, :client_assertion_signing_key, :client_assertion_signing_algorithm
|
|
12
|
+
|
|
13
|
+
def initialize(client_id, domain_url, client_assertion_signing_key, client_assertion_signing_algorithm = nil)
|
|
14
|
+
@client_id = client_id
|
|
15
|
+
@domain_url = domain_url
|
|
16
|
+
@client_assertion_signing_key = client_assertion_signing_key
|
|
17
|
+
@client_assertion_signing_algorithm = client_assertion_signing_algorithm || 'RS256'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def jwt_token
|
|
21
|
+
JWT.encode(jwt_payload, client_assertion_signing_key, client_assertion_signing_algorithm)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def jwt_payload
|
|
27
|
+
{
|
|
28
|
+
iss: client_id,
|
|
29
|
+
sub: client_id,
|
|
30
|
+
aud: File.join(domain_url, '/oauth/token'),
|
|
31
|
+
iat: Time.now.utc.to_i,
|
|
32
|
+
exp: Time.now.utc.to_i + 60,
|
|
33
|
+
jti: SecureRandom.uuid
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -7,6 +7,7 @@ require 'omniauth/auth0/errors'
|
|
|
7
7
|
module OmniAuth
|
|
8
8
|
module Auth0
|
|
9
9
|
# JWT Validator class
|
|
10
|
+
# rubocop:disable Metrics/
|
|
10
11
|
class JWTValidator
|
|
11
12
|
attr_accessor :issuer, :domain
|
|
12
13
|
|
|
@@ -264,12 +265,27 @@ module OmniAuth
|
|
|
264
265
|
end
|
|
265
266
|
|
|
266
267
|
def verify_org(id_token, organization)
|
|
267
|
-
|
|
268
|
+
return unless organization
|
|
269
|
+
|
|
270
|
+
validate_as_id = organization.start_with? 'org_'
|
|
271
|
+
|
|
272
|
+
if validate_as_id
|
|
268
273
|
org_id = id_token['org_id']
|
|
269
274
|
if !org_id || !org_id.is_a?(String)
|
|
270
|
-
raise OmniAuth::Auth0::TokenValidationError
|
|
275
|
+
raise OmniAuth::Auth0::TokenValidationError,
|
|
276
|
+
'Organization Id (org_id) claim must be a string present in the ID token'
|
|
271
277
|
elsif org_id != organization
|
|
272
|
-
raise OmniAuth::Auth0::TokenValidationError
|
|
278
|
+
raise OmniAuth::Auth0::TokenValidationError,
|
|
279
|
+
"Organization Id (org_id) claim value mismatch in the ID token; expected '#{organization}', found '#{org_id}'"
|
|
280
|
+
end
|
|
281
|
+
else
|
|
282
|
+
org_name = id_token['org_name']
|
|
283
|
+
if !org_name || !org_name.is_a?(String)
|
|
284
|
+
raise OmniAuth::Auth0::TokenValidationError,
|
|
285
|
+
'Organization Name (org_name) claim must be a string present in the ID token'
|
|
286
|
+
elsif org_name != organization.downcase
|
|
287
|
+
raise OmniAuth::Auth0::TokenValidationError,
|
|
288
|
+
"Organization Name (org_name) claim value mismatch in the ID token; expected '#{organization}', found '#{org_name}'"
|
|
273
289
|
end
|
|
274
290
|
end
|
|
275
291
|
end
|
|
@@ -4,6 +4,7 @@ require 'base64'
|
|
|
4
4
|
require 'uri'
|
|
5
5
|
require 'securerandom'
|
|
6
6
|
require 'omniauth-oauth2'
|
|
7
|
+
require 'omniauth/auth0/jwt_token'
|
|
7
8
|
require 'omniauth/auth0/jwt_validator'
|
|
8
9
|
require 'omniauth/auth0/telemetry'
|
|
9
10
|
require 'omniauth/auth0/errors'
|
|
@@ -13,6 +14,8 @@ module OmniAuth
|
|
|
13
14
|
# Auth0 OmniAuth strategy
|
|
14
15
|
class Auth0 < OmniAuth::Strategies::OAuth2
|
|
15
16
|
include OmniAuth::Auth0::Telemetry
|
|
17
|
+
AUTHORIZATION_CODE_GRANT_TYPE = 'authorization_code'
|
|
18
|
+
CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
|
16
19
|
|
|
17
20
|
option :name, 'auth0'
|
|
18
21
|
|
|
@@ -28,6 +31,8 @@ module OmniAuth
|
|
|
28
31
|
options.client_options.authorize_url = '/authorize'
|
|
29
32
|
options.client_options.token_url = '/oauth/token'
|
|
30
33
|
options.client_options.userinfo_url = '/userinfo'
|
|
34
|
+
setup_client_options_auth_scheme
|
|
35
|
+
|
|
31
36
|
super
|
|
32
37
|
end
|
|
33
38
|
|
|
@@ -100,25 +105,20 @@ module OmniAuth
|
|
|
100
105
|
end
|
|
101
106
|
|
|
102
107
|
def build_access_token
|
|
108
|
+
options.token_params.merge!(client_assertion_signing_key_token_params) if client_assertion_signing_key_auth?
|
|
103
109
|
options.token_params[:headers] = { 'Auth0-Client' => telemetry_encoded }
|
|
104
110
|
super
|
|
105
111
|
end
|
|
106
112
|
|
|
107
113
|
# Declarative override for the request phase of authentication
|
|
108
114
|
def request_phase
|
|
109
|
-
if no_client_id?
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# Do we have a domain for this Application?
|
|
117
|
-
fail!(:missing_domain)
|
|
118
|
-
else
|
|
119
|
-
# All checks pass, run the Oauth2 request_phase method.
|
|
120
|
-
super
|
|
121
|
-
end
|
|
115
|
+
return fail!(:missing_client_id) if no_client_id?
|
|
116
|
+
return fail!(:missing_client_secret) if no_client_secret?
|
|
117
|
+
return fail!(:missing_domain) if no_domain?
|
|
118
|
+
return fail!(:missing_client_assertion_signing_key) if no_client_assertion_signing_key?
|
|
119
|
+
|
|
120
|
+
# All checks pass, run the Oauth2 request_phase method.
|
|
121
|
+
super
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def callback_phase
|
|
@@ -128,10 +128,32 @@ module OmniAuth
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
private
|
|
131
|
+
|
|
132
|
+
def client_assertion_signing_key_auth?
|
|
133
|
+
options['client_assertion_signing_key']
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def client_assertion_signing_key_token_params
|
|
137
|
+
{
|
|
138
|
+
grant_type: AUTHORIZATION_CODE_GRANT_TYPE,
|
|
139
|
+
client_id: options.client_id,
|
|
140
|
+
client_assertion_type: CLIENT_ASSERTION_TYPE,
|
|
141
|
+
client_assertion: jwt_token
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
131
145
|
def jwt_validator
|
|
132
146
|
@jwt_validator ||= OmniAuth::Auth0::JWTValidator.new(options)
|
|
133
147
|
end
|
|
134
148
|
|
|
149
|
+
def jwt_token
|
|
150
|
+
OmniAuth::Auth0::JWTToken.new(options.client_id,
|
|
151
|
+
domain_url,
|
|
152
|
+
options.client_assertion_signing_key,
|
|
153
|
+
options.client_assertion_signing_algorithm)
|
|
154
|
+
.jwt_token
|
|
155
|
+
end
|
|
156
|
+
|
|
135
157
|
# Parse the raw user info.
|
|
136
158
|
def raw_info
|
|
137
159
|
return @raw_info if @raw_info
|
|
@@ -154,7 +176,7 @@ module OmniAuth
|
|
|
154
176
|
|
|
155
177
|
# Check if the options include a client_secret
|
|
156
178
|
def no_client_secret?
|
|
157
|
-
['', nil].include?(options.client_secret)
|
|
179
|
+
['', nil].include?(options.client_secret) && !options.key?('client_assertion_signing_key')
|
|
158
180
|
end
|
|
159
181
|
|
|
160
182
|
# Check if the options include a domain
|
|
@@ -162,12 +184,24 @@ module OmniAuth
|
|
|
162
184
|
['', nil].include?(options.domain)
|
|
163
185
|
end
|
|
164
186
|
|
|
187
|
+
# Check if the options include a client_assertion_signing_key
|
|
188
|
+
def no_client_assertion_signing_key?
|
|
189
|
+
options.key?('client_assertion_signing_key') && ['', nil].include?(options.client_assertion_signing_key)
|
|
190
|
+
end
|
|
191
|
+
|
|
165
192
|
# Normalize a domain to a URL.
|
|
166
193
|
def domain_url
|
|
167
194
|
domain_url = URI(options.domain)
|
|
168
195
|
domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
|
|
169
196
|
domain_url.to_s
|
|
170
197
|
end
|
|
198
|
+
|
|
199
|
+
# Setup the auth_scheme for the client options if using client assertion signing key
|
|
200
|
+
def setup_client_options_auth_scheme
|
|
201
|
+
return unless client_assertion_signing_key_auth?
|
|
202
|
+
|
|
203
|
+
options.client_options.auth_scheme = :request_body
|
|
204
|
+
end
|
|
171
205
|
end
|
|
172
206
|
end
|
|
173
207
|
end
|
data/omniauth-auth0.gemspec
CHANGED
|
@@ -21,6 +21,7 @@ omniauth-auth0 is the OmniAuth strategy for Auth0.
|
|
|
21
21
|
s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
|
|
22
22
|
s.require_paths = ['lib']
|
|
23
23
|
|
|
24
|
+
s.add_runtime_dependency 'jwt', '~> 2'
|
|
24
25
|
s.add_runtime_dependency 'omniauth', '~> 2'
|
|
25
26
|
s.add_runtime_dependency 'omniauth-oauth2', '~> 1'
|
|
26
27
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'jwt'
|
|
6
|
+
|
|
7
|
+
describe OmniAuth::Auth0::JWTToken do
|
|
8
|
+
let(:client_id) { 'CLIENT_ID' }
|
|
9
|
+
let(:domain_url) { 'https://samples.auth0.com' }
|
|
10
|
+
let(:client_assertion_signing_key) { OpenSSL::PKey::RSA.generate(2048) }
|
|
11
|
+
|
|
12
|
+
describe '#jwt_token' do
|
|
13
|
+
it 'generates a valid JWT token' do
|
|
14
|
+
uuid = '12345678-1234-5678-1234-567812345678'
|
|
15
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
|
16
|
+
|
|
17
|
+
jwt_token = described_class.new(client_id,
|
|
18
|
+
domain_url,
|
|
19
|
+
client_assertion_signing_key,
|
|
20
|
+
'RS256')
|
|
21
|
+
.jwt_token
|
|
22
|
+
decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'RS256' })
|
|
23
|
+
|
|
24
|
+
expect(decoded_token[0]['iss']).to eq(client_id)
|
|
25
|
+
expect(decoded_token[0]['sub']).to eq(client_id)
|
|
26
|
+
expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token")
|
|
27
|
+
expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i)
|
|
28
|
+
expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60)
|
|
29
|
+
expect(decoded_token[0]['jti']).to eq(uuid)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'defaults to RS256 algorithm if not specified' do
|
|
33
|
+
uuid = '12345678-1234-5678-1234-567812345678'
|
|
34
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
|
35
|
+
|
|
36
|
+
jwt_token = described_class.new(client_id, domain_url, client_assertion_signing_key).jwt_token
|
|
37
|
+
decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'RS256' })
|
|
38
|
+
|
|
39
|
+
expect(decoded_token[0]['iss']).to eq(client_id)
|
|
40
|
+
expect(decoded_token[0]['sub']).to eq(client_id)
|
|
41
|
+
expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token")
|
|
42
|
+
expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i)
|
|
43
|
+
expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60)
|
|
44
|
+
expect(decoded_token[0]['jti']).to eq(uuid)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'when using ES256 algorithm' do
|
|
48
|
+
let(:client_assertion_signing_key) { OpenSSL::PKey::EC.generate('prime256v1') }
|
|
49
|
+
|
|
50
|
+
it 'generates a valid JWT token' do
|
|
51
|
+
uuid = '12345678-1234-5678-1234-567812345678'
|
|
52
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
|
53
|
+
jwt_token = described_class.new(client_id,
|
|
54
|
+
domain_url,
|
|
55
|
+
client_assertion_signing_key,
|
|
56
|
+
'ES256')
|
|
57
|
+
.jwt_token
|
|
58
|
+
decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'ES256' })
|
|
59
|
+
|
|
60
|
+
expect(decoded_token[0]['iss']).to eq(client_id)
|
|
61
|
+
expect(decoded_token[0]['sub']).to eq(client_id)
|
|
62
|
+
expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token")
|
|
63
|
+
expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i)
|
|
64
|
+
expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60)
|
|
65
|
+
expect(decoded_token[0]['jti']).to eq(uuid)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'accepts client_assertion_signing_key_token_params as a string' do
|
|
69
|
+
uuid = '12345678-1234-5678-1234-567812345678'
|
|
70
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
|
71
|
+
jwt_token = described_class.new(client_id,
|
|
72
|
+
domain_url,
|
|
73
|
+
client_assertion_signing_key,
|
|
74
|
+
'ES256')
|
|
75
|
+
.jwt_token
|
|
76
|
+
decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'ES256' })
|
|
77
|
+
|
|
78
|
+
expect(decoded_token[0]['iss']).to eq(client_id)
|
|
79
|
+
expect(decoded_token[0]['sub']).to eq(client_id)
|
|
80
|
+
expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token")
|
|
81
|
+
expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i)
|
|
82
|
+
expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60)
|
|
83
|
+
expect(decoded_token[0]['jti']).to eq(uuid)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -476,41 +476,119 @@ describe OmniAuth::Auth0::JWTValidator do
|
|
|
476
476
|
expect(id_token['auth_time']).to eq(auth_time)
|
|
477
477
|
end
|
|
478
478
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
479
|
+
context 'Organization claim validation' do
|
|
480
|
+
it 'should fail when authorize params has organization but org_id is missing in the token' do
|
|
481
|
+
payload = {
|
|
482
|
+
iss: "https://#{domain}/",
|
|
483
|
+
sub: 'sub',
|
|
484
|
+
aud: client_id,
|
|
485
|
+
exp: future_timecode,
|
|
486
|
+
iat: past_timecode
|
|
487
|
+
}
|
|
487
488
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
489
|
+
token = make_hs256_token(payload)
|
|
490
|
+
expect do
|
|
491
|
+
jwt_validator.verify(token, { organization: 'org_123' })
|
|
492
|
+
end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
|
|
493
|
+
message: "Organization Id (org_id) claim must be a string present in the ID token"
|
|
494
|
+
}))
|
|
495
|
+
end
|
|
495
496
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
497
|
+
it 'should fail when authorize params has organization but org_name is missing in the token' do
|
|
498
|
+
payload = {
|
|
499
|
+
iss: "https://#{domain}/",
|
|
500
|
+
sub: 'sub',
|
|
501
|
+
aud: client_id,
|
|
502
|
+
exp: future_timecode,
|
|
503
|
+
iat: past_timecode
|
|
504
|
+
}
|
|
505
505
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
506
|
+
token = make_hs256_token(payload)
|
|
507
|
+
expect do
|
|
508
|
+
jwt_validator.verify(token, { organization: 'my-organization' })
|
|
509
|
+
end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and(having_attributes({
|
|
510
|
+
message: 'Organization Name (org_name) claim must be a string present in the ID token'
|
|
511
|
+
})))
|
|
512
|
+
end
|
|
513
513
|
|
|
514
|
+
it 'should fail when authorize params has organization but token org_id does not match' do
|
|
515
|
+
payload = {
|
|
516
|
+
iss: "https://#{domain}/",
|
|
517
|
+
sub: 'sub',
|
|
518
|
+
aud: client_id,
|
|
519
|
+
exp: future_timecode,
|
|
520
|
+
iat: past_timecode,
|
|
521
|
+
org_id: 'org_5678'
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
token = make_hs256_token(payload)
|
|
525
|
+
expect do
|
|
526
|
+
jwt_validator.verify(token, { organization: 'org_1234' })
|
|
527
|
+
end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and(having_attributes({
|
|
528
|
+
message: "Organization Id (org_id) claim value mismatch in the ID token; expected 'org_1234', found 'org_5678'"
|
|
529
|
+
})))
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
it 'should fail when authorize params has organization but token org_name does not match' do
|
|
533
|
+
payload = {
|
|
534
|
+
iss: "https://#{domain}/",
|
|
535
|
+
sub: 'sub',
|
|
536
|
+
aud: client_id,
|
|
537
|
+
exp: future_timecode,
|
|
538
|
+
iat: past_timecode,
|
|
539
|
+
org_name: 'another-organization'
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
token = make_hs256_token(payload)
|
|
543
|
+
expect do
|
|
544
|
+
jwt_validator.verify(token, { organization: 'my-organization' })
|
|
545
|
+
end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and(having_attributes({
|
|
546
|
+
message: "Organization Name (org_name) claim value mismatch in the ID token; expected 'my-organization', found 'another-organization'"
|
|
547
|
+
})))
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
it 'should not fail when correctly given an organization ID' do
|
|
551
|
+
payload = {
|
|
552
|
+
iss: "https://#{domain}/",
|
|
553
|
+
sub: 'sub',
|
|
554
|
+
aud: client_id,
|
|
555
|
+
exp: future_timecode,
|
|
556
|
+
iat: past_timecode,
|
|
557
|
+
org_id: 'org_1234'
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
token = make_hs256_token(payload)
|
|
561
|
+
jwt_validator.verify(token, { organization: 'org_1234' })
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it 'should not fail when correctly given an organization name' do
|
|
565
|
+
payload = {
|
|
566
|
+
iss: "https://#{domain}/",
|
|
567
|
+
sub: 'sub',
|
|
568
|
+
aud: client_id,
|
|
569
|
+
exp: future_timecode,
|
|
570
|
+
iat: past_timecode,
|
|
571
|
+
org_name: 'my-organization'
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
token = make_hs256_token(payload)
|
|
575
|
+
jwt_validator.verify(token, { organization: 'my-organization' })
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
it 'should not fail when given an organization name in a different casing' do
|
|
579
|
+
payload = {
|
|
580
|
+
iss: "https://#{domain}/",
|
|
581
|
+
sub: 'sub',
|
|
582
|
+
aud: client_id,
|
|
583
|
+
exp: future_timecode,
|
|
584
|
+
iat: past_timecode,
|
|
585
|
+
org_name: 'my-organization'
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
token = make_hs256_token(payload)
|
|
589
|
+
jwt_validator.verify(token, { organization: 'MY-ORGANIZATION' })
|
|
590
|
+
end
|
|
591
|
+
end
|
|
514
592
|
it 'should fail for RS256 token when kid is incorrect' do
|
|
515
593
|
domain = 'example.org'
|
|
516
594
|
sub = 'abc123'
|