gitlab-omniauth-openid-connect 0.5.0 → 0.6.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/CHANGELOG.md +4 -0
- data/lib/omniauth/openid_connect/version.rb +1 -1
- data/lib/omniauth/strategies/openid_connect.rb +35 -10
- data/test/lib/omniauth/strategies/openid_connect_test.rb +80 -37
- data/test/strategy_test_case.rb +24 -0
- metadata +2 -4
- data/test/fixtures/id_token.txt +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13c881e6fc6d97b86a4608afaac9f44737d83650de5f00bb102deab1dc723c89
|
4
|
+
data.tar.gz: 3f116b19d3759309dfd6369671dffdddafe5362c3ecf9c4db16cc25c58b6c4ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80d59151cc0657817732e4d85bdee536fa328c40fc0a16b379172c88b62fc7bd25bfc156a7e10885c16277a0a83984f6e34e6223c47cc746cceaa9c264c7d20f
|
7
|
+
data.tar.gz: 251bbd0f19557183b39c72cc679579ce1550573786bfd41fa213f265e19158f27d491309a6199cdebf081c8ba72bffc6a11d9a25a68d0d7b7bfeac113880c061
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# v0.6.0 (07.08.2021)
|
2
|
+
|
3
|
+
- [Support verification of HS256-signed JWTs](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/8)
|
4
|
+
|
1
5
|
# v0.5.0 (05.07.2021)
|
2
6
|
|
3
7
|
- [Add email_verified field to info dict](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/7)
|
@@ -236,31 +236,55 @@ module OmniAuth
|
|
236
236
|
@access_token
|
237
237
|
end
|
238
238
|
|
239
|
+
# Unlike ::OpenIDConnect::ResponseObject::IdToken.decode, this
|
240
|
+
# method splits the decoding and verification of JWT into two
|
241
|
+
# steps. First, we decode the JWT without verifying it to
|
242
|
+
# determine the algorithm used to sign. Then, we verify it using
|
243
|
+
# the appropriate public key (e.g. if algorithm is RS256) or
|
244
|
+
# shared secret (e.g. if algorithm is HS256). This works around a
|
245
|
+
# limitation in the openid_connect gem:
|
246
|
+
# https://github.com/nov/openid_connect/issues/61
|
239
247
|
def decode_id_token(id_token)
|
240
|
-
decode
|
248
|
+
decoded = JSON::JWT.decode(id_token, :skip_verification)
|
249
|
+
algorithm = decoded.algorithm.to_sym
|
250
|
+
|
251
|
+
keyset =
|
252
|
+
case algorithm
|
253
|
+
when :RS256, :RS384, :RS512
|
254
|
+
public_key
|
255
|
+
when :HS256, :HS384, :HS512
|
256
|
+
client_options.secret
|
257
|
+
end
|
258
|
+
|
259
|
+
decoded.verify!(keyset)
|
260
|
+
::OpenIDConnect::ResponseObject::IdToken.new(decoded)
|
241
261
|
rescue JSON::JWK::Set::KidNotFound
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
262
|
+
# If the JWT has a key ID (kid), then we know that the set of
|
263
|
+
# keys supplied doesn't contain the one we want, and we're
|
264
|
+
# done. However, if there is no kid, then we try each key
|
265
|
+
# individually to see if one works:
|
245
266
|
# https://github.com/nov/json-jwt/pull/92#issuecomment-824654949
|
246
|
-
|
267
|
+
raise if decoded&.header&.key?('kid')
|
268
|
+
|
269
|
+
decoded = decode_with_each_key!(id_token, keyset)
|
247
270
|
|
248
271
|
raise unless decoded
|
249
272
|
|
250
273
|
decoded
|
274
|
+
|
251
275
|
end
|
252
276
|
|
253
277
|
def decode!(id_token, key)
|
254
278
|
::OpenIDConnect::ResponseObject::IdToken.decode(id_token, key)
|
255
279
|
end
|
256
280
|
|
257
|
-
def decode_with_each_key!(id_token)
|
258
|
-
return unless
|
281
|
+
def decode_with_each_key!(id_token, keyset)
|
282
|
+
return unless keyset.is_a?(JSON::JWK::Set)
|
259
283
|
|
260
|
-
|
284
|
+
keyset.each do |key|
|
261
285
|
begin
|
262
286
|
decoded = decode!(id_token, key)
|
263
|
-
rescue JSON::JWS::VerificationFailed
|
287
|
+
rescue JSON::JWS::VerificationFailed, JSON::JWS::UnexpectedAlgorithm, JSON::JWS::UnknownAlgorithm
|
264
288
|
next
|
265
289
|
end
|
266
290
|
|
@@ -304,7 +328,7 @@ module OmniAuth
|
|
304
328
|
end
|
305
329
|
|
306
330
|
def key_or_secret
|
307
|
-
@key_or_secret ||=
|
331
|
+
@key_or_secret ||= begin
|
308
332
|
case options.client_signing_alg&.to_sym
|
309
333
|
when :HS256, :HS384, :HS512
|
310
334
|
client_options.secret
|
@@ -315,6 +339,7 @@ module OmniAuth
|
|
315
339
|
parse_x509_key(options.client_x509_signing_key)
|
316
340
|
end
|
317
341
|
end
|
342
|
+
end
|
318
343
|
end
|
319
344
|
|
320
345
|
def parse_x509_key(key)
|
@@ -168,7 +168,7 @@ module OmniAuth
|
|
168
168
|
|
169
169
|
strategy.options.issuer = 'example.com'
|
170
170
|
strategy.options.client_signing_alg = :RS256
|
171
|
-
strategy.options.client_jwk_signing_key =
|
171
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
172
172
|
strategy.options.response_type = 'code'
|
173
173
|
|
174
174
|
strategy.unstub(:user_info)
|
@@ -177,7 +177,7 @@ module OmniAuth
|
|
177
177
|
access_token.stubs(:refresh_token)
|
178
178
|
access_token.stubs(:expires_in)
|
179
179
|
access_token.stubs(:scope)
|
180
|
-
access_token.stubs(:id_token).returns(
|
180
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
181
181
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
182
182
|
access_token.expects(:userinfo!).returns(user_info)
|
183
183
|
|
@@ -192,14 +192,13 @@ module OmniAuth
|
|
192
192
|
end
|
193
193
|
|
194
194
|
def test_callback_phase_with_id_token
|
195
|
-
code = SecureRandom.hex(16)
|
196
195
|
state = SecureRandom.hex(16)
|
197
|
-
request.stubs(:params).returns('id_token' =>
|
196
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
198
197
|
request.stubs(:path_info).returns('')
|
199
198
|
|
200
199
|
strategy.options.issuer = 'example.com'
|
201
200
|
strategy.options.client_signing_alg = :RS256
|
202
|
-
strategy.options.client_jwk_signing_key =
|
201
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
203
202
|
strategy.options.response_type = 'id_token'
|
204
203
|
|
205
204
|
strategy.unstub(:user_info)
|
@@ -208,7 +207,7 @@ module OmniAuth
|
|
208
207
|
access_token.stubs(:refresh_token)
|
209
208
|
access_token.stubs(:expires_in)
|
210
209
|
access_token.stubs(:scope)
|
211
|
-
access_token.stubs(:id_token).returns(
|
210
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
212
211
|
|
213
212
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
214
213
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -221,14 +220,32 @@ module OmniAuth
|
|
221
220
|
end
|
222
221
|
|
223
222
|
def test_callback_phase_with_id_token_no_kid
|
224
|
-
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
225
223
|
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
226
224
|
|
227
|
-
key = JSON::JWK.new(
|
225
|
+
key = JSON::JWK.new(private_key)
|
228
226
|
other_key = JSON::JWK.new(other_rsa_private)
|
229
|
-
token = JSON::JWT.new(payload).sign(rsa_private, :RS256).to_s
|
230
227
|
state = SecureRandom.hex(16)
|
231
|
-
request.stubs(:params).returns('id_token' =>
|
228
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
229
|
+
request.stubs(:path_info).returns('')
|
230
|
+
|
231
|
+
strategy.options.issuer = issuer
|
232
|
+
strategy.options.client_signing_alg = :RS256
|
233
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, key] }.to_json
|
234
|
+
strategy.options.response_type = 'id_token'
|
235
|
+
|
236
|
+
strategy.unstub(:user_info)
|
237
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
238
|
+
strategy.callback_phase
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_callback_phase_with_id_token_with_kid
|
242
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
243
|
+
|
244
|
+
key = JSON::JWK.new(private_key)
|
245
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
246
|
+
state = SecureRandom.hex(16)
|
247
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
248
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
232
249
|
request.stubs(:path_info).returns('')
|
233
250
|
|
234
251
|
strategy.options.issuer = issuer
|
@@ -241,6 +258,45 @@ module OmniAuth
|
|
241
258
|
strategy.callback_phase
|
242
259
|
end
|
243
260
|
|
261
|
+
def test_callback_phase_with_id_token_with_kid_and_no_matching_kid
|
262
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
263
|
+
|
264
|
+
key = JSON::JWK.new(private_key)
|
265
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
266
|
+
state = SecureRandom.hex(16)
|
267
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
268
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
269
|
+
request.stubs(:path_info).returns('')
|
270
|
+
|
271
|
+
strategy.options.issuer = issuer
|
272
|
+
strategy.options.client_signing_alg = :RS256
|
273
|
+
# We use private_key here instead of the wrapped key, which contains a kid
|
274
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, private_key] }.to_json
|
275
|
+
strategy.options.response_type = 'id_token'
|
276
|
+
|
277
|
+
strategy.unstub(:user_info)
|
278
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
279
|
+
|
280
|
+
assert_raises JSON::JWK::Set::KidNotFound do
|
281
|
+
strategy.callback_phase
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_callback_phase_with_id_token_with_hs256
|
286
|
+
state = SecureRandom.hex(16)
|
287
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
288
|
+
request.stubs(:path_info).returns('')
|
289
|
+
|
290
|
+
strategy.options.issuer = issuer
|
291
|
+
strategy.options.client_options.secret = hmac_secret
|
292
|
+
strategy.options.client_signing_alg = :HS256
|
293
|
+
strategy.options.response_type = 'id_token'
|
294
|
+
|
295
|
+
strategy.unstub(:user_info)
|
296
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
297
|
+
strategy.callback_phase
|
298
|
+
end
|
299
|
+
|
244
300
|
def test_callback_phase_with_id_token_no_matching_key
|
245
301
|
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
246
302
|
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
@@ -266,11 +322,9 @@ module OmniAuth
|
|
266
322
|
end
|
267
323
|
|
268
324
|
def test_callback_phase_with_discovery
|
269
|
-
code = SecureRandom.hex(16)
|
270
325
|
state = SecureRandom.hex(16)
|
271
|
-
jwks = JSON::JWK::Set.new(JSON.parse(File.read('test/fixtures/jwks.json'))['keys'])
|
272
326
|
|
273
|
-
request.stubs(:params).returns('code' =>
|
327
|
+
request.stubs(:params).returns('code' => jwt.to_s, 'state' => state)
|
274
328
|
request.stubs(:path_info).returns('')
|
275
329
|
|
276
330
|
strategy.options.client_options.host = 'example.com'
|
@@ -285,7 +339,7 @@ module OmniAuth
|
|
285
339
|
config.stubs(:token_endpoint).returns('https://example.com/token')
|
286
340
|
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
287
341
|
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
288
|
-
config.stubs(:jwks).returns(jwks)
|
342
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
289
343
|
|
290
344
|
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
291
345
|
|
@@ -300,7 +354,7 @@ module OmniAuth
|
|
300
354
|
access_token.stubs(:refresh_token)
|
301
355
|
access_token.stubs(:expires_in)
|
302
356
|
access_token.stubs(:scope)
|
303
|
-
access_token.stubs(:id_token).returns(
|
357
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
304
358
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
305
359
|
access_token.expects(:userinfo!).returns(user_info)
|
306
360
|
|
@@ -309,9 +363,9 @@ module OmniAuth
|
|
309
363
|
end
|
310
364
|
|
311
365
|
def test_callback_phase_with_jwks_uri
|
312
|
-
|
366
|
+
id_token = jwt.to_s
|
313
367
|
state = SecureRandom.hex(16)
|
314
|
-
request.stubs(:params).returns('id_token' =>
|
368
|
+
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
|
315
369
|
request.stubs(:path_info).returns('')
|
316
370
|
|
317
371
|
strategy.options.issuer = 'example.com'
|
@@ -321,7 +375,7 @@ module OmniAuth
|
|
321
375
|
HTTPClient
|
322
376
|
.any_instance.stubs(:get_content)
|
323
377
|
.with(strategy.options.client_options.jwks_uri)
|
324
|
-
.returns(
|
378
|
+
.returns(jwks.to_json)
|
325
379
|
|
326
380
|
strategy.unstub(:user_info)
|
327
381
|
access_token = stub('OpenIDConnect::AccessToken')
|
@@ -329,7 +383,7 @@ module OmniAuth
|
|
329
383
|
access_token.stubs(:refresh_token)
|
330
384
|
access_token.stubs(:expires_in)
|
331
385
|
access_token.stubs(:scope)
|
332
|
-
access_token.stubs(:id_token).returns(
|
386
|
+
access_token.stubs(:id_token).returns(id_token)
|
333
387
|
|
334
388
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
335
389
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -494,7 +548,7 @@ module OmniAuth
|
|
494
548
|
def test_credentials
|
495
549
|
strategy.options.issuer = 'example.com'
|
496
550
|
strategy.options.client_signing_alg = :RS256
|
497
|
-
strategy.options.client_jwk_signing_key =
|
551
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
498
552
|
|
499
553
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
500
554
|
id_token.stubs(:verify!).returns(true)
|
@@ -505,7 +559,7 @@ module OmniAuth
|
|
505
559
|
access_token.stubs(:refresh_token).returns(SecureRandom.hex(16))
|
506
560
|
access_token.stubs(:expires_in).returns(Time.now)
|
507
561
|
access_token.stubs(:scope).returns('openidconnect')
|
508
|
-
access_token.stubs(:id_token).returns(
|
562
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
509
563
|
|
510
564
|
client.expects(:access_token!).returns(access_token)
|
511
565
|
access_token.expects(:refresh_token).returns(access_token.refresh_token)
|
@@ -592,11 +646,11 @@ module OmniAuth
|
|
592
646
|
strategy.options.issuer = 'foobar.com'
|
593
647
|
strategy.options.client_auth_method = :not_basic
|
594
648
|
strategy.options.client_signing_alg = :RS256
|
595
|
-
strategy.options.client_jwk_signing_key =
|
649
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
596
650
|
|
597
651
|
json_response = {
|
598
652
|
access_token: 'test_access_token',
|
599
|
-
id_token:
|
653
|
+
id_token: jwt.to_s,
|
600
654
|
token_type: 'Bearer',
|
601
655
|
}.to_json
|
602
656
|
success = Struct.new(:status, :body).new(200, json_response)
|
@@ -619,16 +673,14 @@ module OmniAuth
|
|
619
673
|
|
620
674
|
def test_public_key_with_jwks
|
621
675
|
strategy.options.client_signing_alg = :RS256
|
622
|
-
strategy.options.client_jwk_signing_key =
|
676
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
623
677
|
|
624
678
|
assert_equal JSON::JWK::Set, strategy.public_key.class
|
625
679
|
end
|
626
680
|
|
627
681
|
def test_public_key_with_jwk
|
628
682
|
strategy.options.client_signing_alg = :RS256
|
629
|
-
|
630
|
-
jwks = JSON.parse(jwks_str)
|
631
|
-
jwk = jwks['keys'].first
|
683
|
+
jwk = jwks[:keys].first
|
632
684
|
strategy.options.client_jwk_signing_key = jwk.to_json
|
633
685
|
|
634
686
|
assert_equal JSON::JWK, strategy.public_key.class
|
@@ -653,16 +705,7 @@ module OmniAuth
|
|
653
705
|
|
654
706
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
655
707
|
id_token.stubs(:verify!).returns(true)
|
656
|
-
id_token.stubs(:raw_attributes, :to_h).returns(
|
657
|
-
{
|
658
|
-
"iss": "http://server.example.com",
|
659
|
-
"sub": "248289761001",
|
660
|
-
"aud": "s6BhdRkqt3",
|
661
|
-
"nonce": "n-0S6_WzA2Mj",
|
662
|
-
"exp": 1311281970,
|
663
|
-
"iat": 1311280970,
|
664
|
-
}
|
665
|
-
)
|
708
|
+
id_token.stubs(:raw_attributes, :to_h).returns(payload)
|
666
709
|
|
667
710
|
request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token)
|
668
711
|
request.stubs(:path_info).returns('')
|
data/test/strategy_test_case.rb
CHANGED
@@ -27,6 +27,30 @@ class StrategyTestCase < MiniTest::Test
|
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
+
def private_key
|
31
|
+
@private_key ||= OpenSSL::PKey::RSA.generate(512)
|
32
|
+
end
|
33
|
+
|
34
|
+
def jwt
|
35
|
+
@jwt ||= JSON::JWT.new(payload).sign(private_key, :RS256)
|
36
|
+
end
|
37
|
+
|
38
|
+
def hmac_secret
|
39
|
+
@hmac_secret ||= SecureRandom.hex(16)
|
40
|
+
end
|
41
|
+
|
42
|
+
def jwt_with_hs256
|
43
|
+
@jwt_with_hs256 ||= JSON::JWT.new(payload).sign(hmac_secret, :HS256)
|
44
|
+
end
|
45
|
+
|
46
|
+
def jwks
|
47
|
+
@jwks ||= begin
|
48
|
+
key = JSON::JWK.new(private_key)
|
49
|
+
keyset = JSON::JWK::Set.new(key)
|
50
|
+
{ keys: keyset }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
30
54
|
def user_info
|
31
55
|
@user_info ||= OpenIDConnect::ResponseObject::UserInfo.new(
|
32
56
|
sub: SecureRandom.hex(16),
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-omniauth-openid-connect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Bohn
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-07-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
@@ -220,7 +220,6 @@ files:
|
|
220
220
|
- lib/omniauth/openid_connect/version.rb
|
221
221
|
- lib/omniauth/strategies/openid_connect.rb
|
222
222
|
- lib/omniauth_openid_connect.rb
|
223
|
-
- test/fixtures/id_token.txt
|
224
223
|
- test/fixtures/jwks.json
|
225
224
|
- test/fixtures/test.crt
|
226
225
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
@@ -250,7 +249,6 @@ signing_key:
|
|
250
249
|
specification_version: 4
|
251
250
|
summary: OpenID Connect Strategy for OmniAuth
|
252
251
|
test_files:
|
253
|
-
- test/fixtures/id_token.txt
|
254
252
|
- test/fixtures/jwks.json
|
255
253
|
- test/fixtures/test.crt
|
256
254
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
data/test/fixtures/id_token.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg
|