|
277 | 277 | let(:logger_output) { StringIO.new } |
278 | 278 | let(:logger) { Logger.new(logger_output) } |
279 | 279 |
|
280 | | - it 'works as expected' do |
| 280 | + it 'works as expected (legacy)' do |
281 | 281 | jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid') |
282 | 282 | payload = { data: 'data' } |
283 | 283 | headers = { kid: jwk.kid } |
|
322 | 322 | token = JWT.encode(payload, jwk.keypair, 'RS512', headers) |
323 | 323 | expect { JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) }.to raise_error(JWT::DecodeError, 'Could not find public key for kid yet-another-new-kid') |
324 | 324 | end |
| 325 | + |
| 326 | + it 'works as expected' do |
| 327 | + jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid: 'optional-kid') |
| 328 | + payload = { data: 'data' } |
| 329 | + headers = { kid: jwk.kid } |
| 330 | + |
| 331 | + token = JWT.encode(payload, jwk.keypair, 'RS512', headers) |
| 332 | + |
| 333 | + # The jwk loader would fetch the set of JWKs from a trusted source, |
| 334 | + # to avoid malicious invalidations some kind of protection needs to be implemented. |
| 335 | + # This example only allows cache invalidations every 5 minutes. |
| 336 | + jwk_loader = ->(options) do |
| 337 | + if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300 |
| 338 | + logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache") |
| 339 | + @cached_keys = nil |
| 340 | + end |
| 341 | + @cached_keys ||= begin |
| 342 | + @cache_last_update = Time.now.to_i |
| 343 | + { keys: [jwk.export] } |
| 344 | + end |
| 345 | + end |
| 346 | + |
| 347 | + begin |
| 348 | + JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) |
| 349 | + rescue JWT::JWKError |
| 350 | + # Handle problems with the provided JWKs |
| 351 | + rescue JWT::DecodeError |
| 352 | + # Handle other decode related issues e.g. no kid in header, no matching public key found etc. |
| 353 | + end |
| 354 | + |
| 355 | + ## This is not in the example but verifies that the cache is invalidated after 5 minutes |
| 356 | + jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'new-kid') |
| 357 | + |
| 358 | + headers = { kid: jwk.kid } |
| 359 | + |
| 360 | + token = JWT.encode(payload, jwk.keypair, 'RS512', headers) |
| 361 | + @cache_last_update = Time.now.to_i - 301 |
| 362 | + |
| 363 | + JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) |
| 364 | + expect(logger_output.string.chomp).to match(/^I, .* : Invalidating JWK cache. new-kid not found from previous cache/) |
| 365 | + |
| 366 | + jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'yet-another-new-kid') |
| 367 | + headers = { kid: jwk.kid } |
| 368 | + token = JWT.encode(payload, jwk.keypair, 'RS512', headers) |
| 369 | + expect { JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) }.to raise_error(JWT::DecodeError, 'Could not find public key for kid yet-another-new-kid') |
| 370 | + end |
325 | 371 | end |
326 | 372 |
|
327 | 373 | it 'JWK with thumbprint as kid via symbol' do |
|
344 | 390 | expect(jwk_hash[:kid].size).to eq(43) |
345 | 391 | end |
346 | 392 |
|
347 | | - it 'JWK with thumbprint given in the initializer' do |
| 393 | + it 'JWK with thumbprint given in the initializer (legacy)' do |
348 | 394 | jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: ::JWT::JWK::Thumbprint) |
349 | 395 |
|
350 | 396 | jwk_hash = jwk.export |
351 | 397 |
|
352 | 398 | expect(jwk_hash[:kid].size).to eq(43) |
353 | 399 | end |
| 400 | + |
| 401 | + it 'JWK with thumbprint given in the initializer' do |
| 402 | + jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint) |
| 403 | + |
| 404 | + jwk_hash = jwk.export |
| 405 | + |
| 406 | + expect(jwk_hash[:kid].size).to eq(43) |
| 407 | + end |
354 | 408 | end |
355 | 409 |
|
356 | 410 | context 'custom algorithm example' do |
|
0 commit comments