Skip to content

Commit a6e2ac0

Browse files
bellebaumanakinj
authored andcommitted
Update JWK examples in README.md
1 parent 20a84ab commit a6e2ac0

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ JWK is a JSON structure representing a cryptographic key. Currently only support
574574
If the kid is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`. The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
575575

576576
```ruby
577-
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid')
577+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid: 'optional-kid')
578578
payload = { data: 'data' }
579579
headers = { kid: jwk.kid }
580580

@@ -615,7 +615,9 @@ JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
615615
The ::JWT::JWK class can be used to import and export both the public key (default behaviour) and the private key. To include the private key in the export pass the `include_private` parameter to the export method.
616616

617617
```ruby
618-
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
618+
# You can optionally add descriptive parameters to the JWK
619+
desc_params = { kid: 'my-kid', use: 'sig' }
620+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
619621

620622
jwk_hash = jwk.export
621623
jwk_hash_with_private_key = jwk.export(include_private: true)
@@ -630,7 +632,7 @@ JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
630632
# OR
631633
JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
632634
# OR
633-
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: ::JWT::JWK::Thumbprint)
635+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
634636

635637
jwk_hash = jwk.export
636638

spec/integration/readme_examples_spec.rb

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@
277277
let(:logger_output) { StringIO.new }
278278
let(:logger) { Logger.new(logger_output) }
279279

280-
it 'works as expected' do
280+
it 'works as expected (legacy)' do
281281
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid')
282282
payload = { data: 'data' }
283283
headers = { kid: jwk.kid }
@@ -322,6 +322,52 @@
322322
token = JWT.encode(payload, jwk.keypair, 'RS512', headers)
323323
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')
324324
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
325371
end
326372

327373
it 'JWK with thumbprint as kid via symbol' do
@@ -344,13 +390,21 @@
344390
expect(jwk_hash[:kid].size).to eq(43)
345391
end
346392

347-
it 'JWK with thumbprint given in the initializer' do
393+
it 'JWK with thumbprint given in the initializer (legacy)' do
348394
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: ::JWT::JWK::Thumbprint)
349395

350396
jwk_hash = jwk.export
351397

352398
expect(jwk_hash[:kid].size).to eq(43)
353399
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
354408
end
355409

356410
context 'custom algorithm example' do

0 commit comments

Comments
 (0)