omniauth-auth0 2.0.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of omniauth-auth0 might be problematic. Click here for more details.

@@ -0,0 +1,36 @@
1
+ require 'json'
2
+
3
+ module OmniAuth
4
+ module Auth0
5
+ # Module to provide necessary telemetry for API requests.
6
+ module Telemetry
7
+
8
+ # Return a telemetry hash to be encoded and sent to Auth0.
9
+ # @return hash
10
+ def telemetry
11
+ telemetry = {
12
+ name: 'omniauth-auth0',
13
+ version: OmniAuth::Auth0::VERSION,
14
+ env: {
15
+ ruby: RUBY_VERSION
16
+ }
17
+ }
18
+ add_rails_version telemetry
19
+ end
20
+
21
+ # JSON-ify and base64 encode the current telemetry.
22
+ # @return string
23
+ def telemetry_encoded
24
+ Base64.urlsafe_encode64(JSON.dump(telemetry))
25
+ end
26
+
27
+ private
28
+
29
+ def add_rails_version(telemetry)
30
+ return telemetry unless Gem.loaded_specs['rails'].respond_to? :version
31
+ telemetry[:env][:rails] = Gem.loaded_specs['rails'].version.to_s
32
+ telemetry
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,19 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
  require 'uri'
5
+ require 'securerandom'
3
6
  require 'omniauth-oauth2'
7
+ require 'omniauth/auth0/jwt_validator'
8
+ require 'omniauth/auth0/telemetry'
9
+ require 'omniauth/auth0/errors'
4
10
 
5
11
  module OmniAuth
6
12
  module Strategies
7
13
  # Auth0 OmniAuth strategy
8
14
  class Auth0 < OmniAuth::Strategies::OAuth2
15
+ include OmniAuth::Auth0::Telemetry
16
+
9
17
  option :name, 'auth0'
10
18
 
11
- args [
12
- :client_id,
13
- :client_secret,
14
- :domain
19
+ args %i[
20
+ client_id
21
+ client_secret
22
+ domain
15
23
  ]
16
24
 
25
+ # Setup client URLs used during authentication
17
26
  def client
18
27
  options.client_options.site = domain_url
19
28
  options.client_options.authorize_url = '/authorize'
@@ -22,25 +31,48 @@ module OmniAuth
22
31
  super
23
32
  end
24
33
 
34
+ # Use the "sub" key of the userinfo returned
35
+ # as the uid (globally unique string identifier).
25
36
  uid { raw_info['sub'] }
26
37
 
38
+ # Build the API credentials hash with returned auth data.
27
39
  credentials do
28
- hash = { 'token' => access_token.token }
29
- hash['expires'] = true
40
+ credentials = {
41
+ 'token' => access_token.token,
42
+ 'expires' => true
43
+ }
44
+
30
45
  if access_token.params
31
- hash['id_token'] = access_token.params['id_token']
32
- hash['token_type'] = access_token.params['token_type']
33
- hash['refresh_token'] = access_token.refresh_token
46
+ credentials.merge!(
47
+ 'id_token' => access_token.params['id_token'],
48
+ 'token_type' => access_token.params['token_type'],
49
+ 'refresh_token' => access_token.refresh_token
50
+ )
51
+ end
52
+
53
+ # Retrieve and remove authorization params from the session
54
+ session_authorize_params = session['authorize_params'] || {}
55
+ session.delete('authorize_params')
56
+
57
+ auth_scope = session_authorize_params[:scope]
58
+ if auth_scope.respond_to?(:include?) && auth_scope.include?('openid')
59
+ # Make sure the ID token can be verified and decoded.
60
+ auth0_jwt = OmniAuth::Auth0::JWTValidator.new(options)
61
+ auth0_jwt.verify(credentials['id_token'], session_authorize_params)
34
62
  end
35
- hash
63
+
64
+ credentials
36
65
  end
37
66
 
67
+ # Store all raw information for use in the session.
38
68
  extra do
39
69
  {
40
70
  raw_info: raw_info
41
71
  }
42
72
  end
43
73
 
74
+ # Build a hash of information about the user
75
+ # with keys taken from the Auth Hash Schema.
44
76
  info do
45
77
  {
46
78
  name: raw_info['name'] || raw_info['sub'],
@@ -50,56 +82,82 @@ module OmniAuth
50
82
  }
51
83
  end
52
84
 
85
+ # Define the parameters used for the /authorize endpoint
53
86
  def authorize_params
54
87
  params = super
55
- params['auth0Client'] = client_info
88
+ parsed_query = Rack::Utils.parse_query(request.query_string)
89
+ %w[connection connection_scope prompt screen_hint].each do |key|
90
+ params[key] = parsed_query[key] if parsed_query.key?(key)
91
+ end
92
+
93
+ # Generate nonce
94
+ params[:nonce] = SecureRandom.hex
95
+ # Generate leeway if none exists
96
+ params[:leeway] = 60 unless params[:leeway]
97
+
98
+ # Store authorize params in the session for token verification
99
+ session['authorize_params'] = params
100
+
56
101
  params
57
102
  end
58
103
 
104
+ def build_access_token
105
+ options.token_params[:headers] = { 'Auth0-Client' => telemetry_encoded }
106
+ super
107
+ end
108
+
109
+ # Declarative override for the request phase of authentication
59
110
  def request_phase
60
111
  if no_client_id?
112
+ # Do we have a client_id for this Application?
61
113
  fail!(:missing_client_id)
62
114
  elsif no_client_secret?
115
+ # Do we have a client_secret for this Application?
63
116
  fail!(:missing_client_secret)
64
117
  elsif no_domain?
118
+ # Do we have a domain for this Application?
65
119
  fail!(:missing_domain)
66
120
  else
121
+ # All checks pass, run the Oauth2 request_phase method.
67
122
  super
68
123
  end
69
124
  end
70
125
 
126
+ def callback_phase
127
+ super
128
+ rescue OmniAuth::Auth0::TokenValidationError => e
129
+ fail!(:token_validation_error, e)
130
+ end
131
+
71
132
  private
72
133
 
134
+ # Parse the raw user info.
73
135
  def raw_info
74
136
  userinfo_url = options.client_options.userinfo_url
75
137
  @raw_info ||= access_token.get(userinfo_url).parsed
76
138
  end
77
139
 
140
+ # Check if the options include a client_id
78
141
  def no_client_id?
79
142
  ['', nil].include?(options.client_id)
80
143
  end
81
144
 
145
+ # Check if the options include a client_secret
82
146
  def no_client_secret?
83
147
  ['', nil].include?(options.client_secret)
84
148
  end
85
149
 
150
+ # Check if the options include a domain
86
151
  def no_domain?
87
152
  ['', nil].include?(options.domain)
88
153
  end
89
154
 
155
+ # Normalize a domain to a URL.
90
156
  def domain_url
91
157
  domain_url = URI(options.domain)
92
158
  domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
93
159
  domain_url.to_s
94
160
  end
95
-
96
- def client_info
97
- client_info = JSON.dump(
98
- name: 'omniauth-auth0',
99
- version: OmniAuth::Auth0::VERSION
100
- )
101
- Base64.urlsafe_encode64(client_info)
102
- end
103
161
  end
104
162
  end
105
163
  end
@@ -8,22 +8,20 @@ Gem::Specification.new do |s|
8
8
  s.authors = ['Auth0']
9
9
  s.email = ['info@auth0.com']
10
10
  s.homepage = 'https://github.com/auth0/omniauth-auth0'
11
- s.summary = 'Omniauth OAuth2 strategy for the Auth0 platform.'
11
+ s.summary = 'OmniAuth OAuth2 strategy for the Auth0 platform.'
12
12
  s.description = %q{Auth0 is an authentication broker that supports social identity providers as well as enterprise identity providers such as Active Directory, LDAP, Google Apps, Salesforce.
13
13
 
14
14
  OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible.
15
15
 
16
- omniauth-auth0 is the omniauth strategy for Auth0.
16
+ omniauth-auth0 is the OmniAuth strategy for Auth0.
17
17
  }
18
18
 
19
- s.rubyforge_project = 'omniauth-auth0'
20
-
21
19
  s.files = `git ls-files`.split("\n")
22
20
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
21
  s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
24
22
  s.require_paths = ['lib']
25
23
 
26
- s.add_runtime_dependency 'omniauth-oauth2', '~> 1.4'
24
+ s.add_runtime_dependency 'omniauth-oauth2', '~> 1.5'
27
25
 
28
26
  s.add_development_dependency 'bundler', '~> 1.9'
29
27
 
@@ -0,0 +1,501 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+ require 'jwt'
4
+
5
+ describe OmniAuth::Auth0::JWTValidator do
6
+ #
7
+ # Reused data
8
+ #
9
+
10
+ let(:client_id) { 'CLIENT_ID' }
11
+ let(:client_secret) { 'CLIENT_SECRET' }
12
+ let(:domain) { 'samples.auth0.com' }
13
+ let(:future_timecode) { 32_503_680_000 }
14
+ let(:past_timecode) { 303_912_000 }
15
+ let(:jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
16
+
17
+ let(:rsa_private_key) do
18
+ OpenSSL::PKey::RSA.generate 2048
19
+ end
20
+
21
+ let(:rsa_token_jwks) do
22
+ {
23
+ keys: [
24
+ {
25
+ kid: jwks_kid,
26
+ x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)]
27
+ }
28
+ ]
29
+ }.to_json
30
+ end
31
+
32
+ let(:jwks) do
33
+ current_dir = File.dirname(__FILE__)
34
+ jwks_file = File.read("#{current_dir}/../../resources/jwks.json")
35
+ JSON.parse(jwks_file, symbolize_names: true)
36
+ end
37
+
38
+ #
39
+ # Specs
40
+ #
41
+
42
+ describe 'JWT verifier default values' do
43
+ let(:jwt_validator) do
44
+ make_jwt_validator
45
+ end
46
+
47
+ it 'should have the correct issuer' do
48
+ expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
49
+ end
50
+ end
51
+
52
+ describe 'JWT verifier token_head' do
53
+ let(:jwt_validator) do
54
+ make_jwt_validator
55
+ end
56
+
57
+ it 'should parse the head of a valid JWT' do
58
+ expect(jwt_validator.token_head(make_hs256_token)[:alg]).to eq('HS256')
59
+ end
60
+
61
+ it 'should fail parsing the head of a blank JWT' do
62
+ expect(jwt_validator.token_head('')).to eq({})
63
+ end
64
+
65
+ it 'should fail parsing the head of an invalid JWT' do
66
+ expect(jwt_validator.token_head('.')).to eq({})
67
+ end
68
+
69
+ it 'should throw an exception for invalid JSON' do
70
+ expect do
71
+ jwt_validator.token_head('QXV0aDA=')
72
+ end.to raise_error(JSON::ParserError)
73
+ end
74
+ end
75
+
76
+ describe 'JWT verifier jwks_public_cert' do
77
+ let(:jwt_validator) do
78
+ make_jwt_validator
79
+ end
80
+
81
+ it 'should return a public_key' do
82
+ x5c = jwks[:keys].first[:x5c].first
83
+ public_cert = jwt_validator.jwks_public_cert(x5c)
84
+ expect(public_cert.instance_of?(OpenSSL::PKey::RSA)).to eq(true)
85
+ end
86
+
87
+ it 'should fail with an invalid x5c' do
88
+ expect do
89
+ jwt_validator.jwks_public_cert('QXV0aDA=')
90
+ end.to raise_error(OpenSSL::X509::CertificateError)
91
+ end
92
+ end
93
+
94
+ describe 'JWT verifier jwks_key' do
95
+ let(:jwt_validator) do
96
+ make_jwt_validator
97
+ end
98
+
99
+ before do
100
+ stub_jwks
101
+ end
102
+
103
+ it 'should return a key' do
104
+ expect(jwt_validator.jwks_key(:alg, jwks_kid)).to eq('RS256')
105
+ end
106
+
107
+ it 'should return an x5c key' do
108
+ expect(jwt_validator.jwks_key(:x5c, jwks_kid).length).to eq(1)
109
+ end
110
+
111
+ it 'should return nil if there is not key' do
112
+ expect(jwt_validator.jwks_key(:auth0, jwks_kid)).to eq(nil)
113
+ end
114
+
115
+ it 'should return nil if the key ID is invalid' do
116
+ expect(jwt_validator.jwks_key(:alg, "#{jwks_kid}_invalid")).to eq(nil)
117
+ end
118
+ end
119
+
120
+ describe 'JWT verifier custom issuer' do
121
+ context 'same as domain' do
122
+ let(:jwt_validator) do
123
+ make_jwt_validator(opt_issuer: domain)
124
+ end
125
+
126
+ it 'should have the correct issuer' do
127
+ expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
128
+ end
129
+
130
+ it 'should have the correct domain' do
131
+ expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
132
+ end
133
+ end
134
+
135
+ context 'different from domain' do
136
+ let(:jwt_validator) do
137
+ make_jwt_validator(opt_issuer: 'different.auth0.com')
138
+ end
139
+
140
+ it 'should have the correct issuer' do
141
+ expect(jwt_validator.issuer).to eq('https://different.auth0.com/')
142
+ end
143
+
144
+ it 'should have the correct domain' do
145
+ expect(jwt_validator.domain).to eq('https://samples.auth0.com/')
146
+ end
147
+ end
148
+ end
149
+
150
+ describe 'JWT verifier verify' do
151
+ let(:jwt_validator) do
152
+ make_jwt_validator
153
+ end
154
+
155
+ before do
156
+ stub_jwks
157
+ stub_dummy_jwks
158
+ end
159
+
160
+ it 'should fail with missing issuer' do
161
+ expect do
162
+ jwt_validator.verify(make_hs256_token)
163
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
164
+ message: "Issuer (iss) claim must be a string present in the ID token"
165
+ }))
166
+ end
167
+
168
+ it 'should fail with invalid issuer' do
169
+ payload = {
170
+ iss: 'https://auth0.com/'
171
+ }
172
+ token = make_hs256_token(payload)
173
+ expect do
174
+ jwt_validator.verify(token)
175
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
176
+ message: "Issuer (iss) claim mismatch in the ID token, expected (https://samples.auth0.com/), found (https://auth0.com/)"
177
+ }))
178
+ end
179
+
180
+ it 'should fail when subject is missing' do
181
+ payload = {
182
+ iss: "https://#{domain}/",
183
+ sub: ''
184
+ }
185
+ token = make_hs256_token(payload)
186
+ expect do
187
+ jwt_validator.verify(token)
188
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
189
+ message: "Subject (sub) claim must be a string present in the ID token"
190
+ }))
191
+ end
192
+
193
+ it 'should fail with missing audience' do
194
+ payload = {
195
+ iss: "https://#{domain}/",
196
+ sub: 'sub'
197
+ }
198
+ token = make_hs256_token(payload)
199
+ expect do
200
+ jwt_validator.verify(token)
201
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
202
+ message: "Audience (aud) claim must be a string or array of strings present in the ID token"
203
+ }))
204
+ end
205
+
206
+ it 'should fail with invalid audience' do
207
+ payload = {
208
+ iss: "https://#{domain}/",
209
+ sub: 'sub',
210
+ aud: 'Auth0'
211
+ }
212
+ token = make_hs256_token(payload)
213
+ expect do
214
+ jwt_validator.verify(token)
215
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
216
+ message: "Audience (aud) claim mismatch in the ID token; expected #{client_id} but found Auth0"
217
+ }))
218
+ end
219
+
220
+ it 'should fail when missing expiration' do
221
+ payload = {
222
+ iss: "https://#{domain}/",
223
+ sub: 'sub',
224
+ aud: client_id
225
+ }
226
+
227
+ token = make_hs256_token(payload)
228
+ expect do
229
+ jwt_validator.verify(token)
230
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
231
+ message: "Expiration time (exp) claim must be a number present in the ID token"
232
+ }))
233
+ end
234
+
235
+ it 'should fail when past expiration' do
236
+ payload = {
237
+ iss: "https://#{domain}/",
238
+ sub: 'sub',
239
+ aud: client_id,
240
+ exp: past_timecode
241
+ }
242
+
243
+ token = make_hs256_token(payload)
244
+ expect do
245
+ jwt_validator.verify(token)
246
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
247
+ message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(past_timecode + 60)})"
248
+ }))
249
+ end
250
+
251
+ it 'should fail when missing iat' do
252
+ payload = {
253
+ iss: "https://#{domain}/",
254
+ sub: 'sub',
255
+ aud: client_id,
256
+ exp: future_timecode
257
+ }
258
+
259
+ token = make_hs256_token(payload)
260
+ expect do
261
+ jwt_validator.verify(token)
262
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
263
+ message: "Issued At (iat) claim must be a number present in the ID token"
264
+ }))
265
+ end
266
+
267
+ it 'should fail when authorize params has nonce but nonce is missing in the token' do
268
+ payload = {
269
+ iss: "https://#{domain}/",
270
+ sub: 'sub',
271
+ aud: client_id,
272
+ exp: future_timecode,
273
+ iat: past_timecode
274
+ }
275
+
276
+ token = make_hs256_token(payload)
277
+ expect do
278
+ jwt_validator.verify(token, { nonce: 'noncey' })
279
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
280
+ message: "Nonce (nonce) claim must be a string present in the ID token"
281
+ }))
282
+ end
283
+
284
+ it 'should fail when authorize params has nonce but token nonce does not match' do
285
+ payload = {
286
+ iss: "https://#{domain}/",
287
+ sub: 'sub',
288
+ aud: client_id,
289
+ exp: future_timecode,
290
+ iat: past_timecode,
291
+ nonce: 'mismatch'
292
+ }
293
+
294
+ token = make_hs256_token(payload)
295
+ expect do
296
+ jwt_validator.verify(token, { nonce: 'noncey' })
297
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
298
+ message: "Nonce (nonce) claim value mismatch in the ID token; expected (noncey), found (mismatch)"
299
+ }))
300
+ end
301
+
302
+ it 'should fail when “aud” is an array of strings and azp claim is not present' do
303
+ aud = [
304
+ client_id,
305
+ "https://#{domain}/userinfo"
306
+ ]
307
+ payload = {
308
+ iss: "https://#{domain}/",
309
+ sub: 'sub',
310
+ aud: aud,
311
+ exp: future_timecode,
312
+ iat: past_timecode
313
+ }
314
+
315
+ token = make_hs256_token(payload)
316
+ expect do
317
+ jwt_validator.verify(token)
318
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
319
+ message: "Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values"
320
+ }))
321
+ end
322
+
323
+ it 'should fail when "azp" claim doesnt match the expected aud' do
324
+ aud = [
325
+ client_id,
326
+ "https://#{domain}/userinfo"
327
+ ]
328
+ payload = {
329
+ iss: "https://#{domain}/",
330
+ sub: 'sub',
331
+ aud: aud,
332
+ exp: future_timecode,
333
+ iat: past_timecode,
334
+ azp: 'not_expected'
335
+ }
336
+
337
+ token = make_hs256_token(payload)
338
+ expect do
339
+ jwt_validator.verify(token)
340
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
341
+ message: "Authorized Party (azp) claim mismatch in the ID token; expected (#{client_id}), found (not_expected)"
342
+ }))
343
+ end
344
+
345
+ it 'should fail when “max_age” sent on the authentication request and this claim is not present' do
346
+ payload = {
347
+ iss: "https://#{domain}/",
348
+ sub: 'sub',
349
+ aud: client_id,
350
+ exp: future_timecode,
351
+ iat: past_timecode
352
+ }
353
+
354
+ token = make_hs256_token(payload)
355
+ expect do
356
+ jwt_validator.verify(token, { max_age: 60 })
357
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
358
+ message: "Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified"
359
+ }))
360
+ end
361
+
362
+ it 'should fail when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future' do
363
+ payload = {
364
+ iss: "https://#{domain}/",
365
+ sub: 'sub',
366
+ aud: client_id,
367
+ exp: future_timecode,
368
+ iat: past_timecode,
369
+ auth_time: past_timecode
370
+ }
371
+
372
+ token = make_hs256_token(payload)
373
+ expect do
374
+ jwt_validator.verify(token, { max_age: 60 })
375
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
376
+ message: "Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (#{Time.now}) is after last auth time (#{Time.at(past_timecode + 60 + 60)})"
377
+ }))
378
+ end
379
+
380
+ it 'should verify a valid HS256 token with multiple audiences' do
381
+ audience = [
382
+ client_id,
383
+ "https://#{domain}/userinfo"
384
+ ]
385
+ payload = {
386
+ iss: "https://#{domain}/",
387
+ sub: 'sub',
388
+ aud: audience,
389
+ exp: future_timecode,
390
+ iat: past_timecode,
391
+ azp: client_id
392
+ }
393
+ token = make_hs256_token(payload)
394
+ id_token = jwt_validator.verify(token)
395
+ expect(id_token['aud']).to eq(audience)
396
+ end
397
+
398
+ it 'should verify a standard HS256 token' do
399
+ sub = 'abc123'
400
+ payload = {
401
+ iss: "https://#{domain}/",
402
+ sub: sub,
403
+ aud: client_id,
404
+ exp: future_timecode,
405
+ iat: past_timecode
406
+ }
407
+ token = make_hs256_token(payload)
408
+ verified_token = jwt_validator.verify(token)
409
+ expect(verified_token['sub']).to eq(sub)
410
+ end
411
+
412
+ it 'should verify a standard RS256 token' do
413
+ domain = 'example.org'
414
+ sub = 'abc123'
415
+ payload = {
416
+ sub: sub,
417
+ exp: future_timecode,
418
+ iss: "https://#{domain}/",
419
+ iat: past_timecode,
420
+ aud: client_id,
421
+ kid: jwks_kid
422
+ }
423
+ token = make_rs256_token(payload)
424
+ verified_token = make_jwt_validator(opt_domain: domain).verify(token)
425
+ expect(verified_token['sub']).to eq(sub)
426
+ end
427
+ end
428
+
429
+ private
430
+
431
+ def make_jwt_validator(opt_domain: domain, opt_issuer: nil)
432
+ opts = OpenStruct.new(
433
+ domain: opt_domain,
434
+ client_id: client_id,
435
+ client_secret: client_secret
436
+ )
437
+ opts[:issuer] = opt_issuer unless opt_issuer.nil?
438
+
439
+ OmniAuth::Auth0::JWTValidator.new(opts)
440
+ end
441
+
442
+ def make_hs256_token(payload = nil)
443
+ payload = { sub: 'abc123' } if payload.nil?
444
+ JWT.encode payload, client_secret, 'HS256'
445
+ end
446
+
447
+ def make_rs256_token(payload = nil)
448
+ payload = { sub: 'abc123' } if payload.nil?
449
+ JWT.encode payload, rsa_private_key, 'RS256', kid: jwks_kid
450
+ end
451
+
452
+ def make_cert(private_key)
453
+ cert = OpenSSL::X509::Certificate.new
454
+ cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0')
455
+ cert.subject = cert.issuer
456
+ cert.not_before = Time.now
457
+ cert.not_after = Time.now + 365 * 24 * 60 * 60
458
+ cert.public_key = private_key.public_key
459
+ cert.serial = 0x0
460
+ cert.version = 2
461
+
462
+ ef = OpenSSL::X509::ExtensionFactory.new
463
+ ef.subject_certificate = cert
464
+ ef.issuer_certificate = cert
465
+ cert.extensions = [
466
+ ef.create_extension('basicConstraints', 'CA:TRUE', true),
467
+ ef.create_extension('subjectKeyIdentifier', 'hash')
468
+ ]
469
+ cert.add_extension ef.create_extension(
470
+ 'authorityKeyIdentifier',
471
+ 'keyid:always,issuer:always'
472
+ )
473
+
474
+ cert.sign private_key, OpenSSL::Digest::SHA1.new
475
+ end
476
+
477
+ def stub_jwks
478
+ stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json')
479
+ .to_return(
480
+ headers: { 'Content-Type' => 'application/json' },
481
+ body: jwks.to_json,
482
+ status: 200
483
+ )
484
+ end
485
+
486
+ def stub_bad_jwks
487
+ stub_request(:get, 'https://samples.auth0.com/.well-known/jwks-bad.json')
488
+ .to_return(
489
+ status: 404
490
+ )
491
+ end
492
+
493
+ def stub_dummy_jwks
494
+ stub_request(:get, 'https://example.org/.well-known/jwks.json')
495
+ .to_return(
496
+ headers: { 'Content-Type' => 'application/json' },
497
+ body: rsa_token_jwks,
498
+ status: 200
499
+ )
500
+ end
501
+ end