Skip to content

Commit 95dc43f

Browse files
committed
Better support for JWK keyfinder and EncodedToken
1 parent 014d4b3 commit 95dc43f

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
1919
**Features:**
2020
- JWT::EncodedToken#verify! method that bundles signature and claim validation [#647](https://github.com/jwt/ruby-jwt/pull/647) ([@anakinj](https://github.com/anakinj))
2121
- Do not override the alg header if already given [#659](https://github.com/jwt/ruby-jwt/pull/659) ([@anakinj](https://github.com/anakinj))
22+
- Make `JWK::KeyFinder` compatible with `JWT::EncodedToken` [#663](https://github.com/jwt/ruby-jwt/pull/663) ([@anakinj](https://github.com/anakinj))
2223
- Your contribution here
2324

2425
**Fixes and enhancements:**

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,35 @@ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
296296
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
297297
```
298298

299+
A keyfinder can be used to verify a signature. A keyfinder is an object responding to the `#call` method. The method expects to receive one argument, which is the token to be verified.
300+
301+
An example on using the built-in JWK keyfinder:
302+
```ruby
303+
# Create and sign a token
304+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
305+
token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
306+
token.sign!(algorithm: 'RS256', key: jwk.signing_key)
307+
308+
# Create keyfinder object, verify and decode token
309+
key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
310+
encoded_token = JWT::EncodedToken.new(token.jwt)
311+
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
312+
encoded_token.payload # => { 'pay' => 'load' }
313+
```
314+
315+
Using a custom keyfinder proc:
316+
```ruby
317+
# Create and sign a token
318+
key = OpenSSL::PKey::RSA.generate(2048)
319+
token = JWT::Token.new(payload: { pay: 'load' })
320+
token.sign!(algorithm: 'RS256', key: key)
321+
322+
# Verify and decode token
323+
encoded_token = JWT::EncodedToken.new(token.jwt)
324+
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
325+
encoded_token.payload # => { 'pay' => 'load' }
326+
```
327+
299328
### Detached payload
300329

301330
The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.

lib/jwt/jwk/key_finder.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
module JWT
44
module JWK
5-
# @api private
5+
# JSON Web Key keyfinder
6+
# To find the key for a given kid
67
class KeyFinder
8+
# Initializes a new KeyFinder instance.
9+
# @param [Hash] options the options to create a KeyFinder with
10+
# @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc
11+
# @option options [Boolean] :allow_nil_kid whether to allow nil kid
712
def initialize(options)
813
@allow_nil_kid = options[:allow_nil_kid]
914
jwks_or_loader = options[:jwks]
@@ -15,6 +20,8 @@ def initialize(options)
1520
end
1621
end
1722

23+
# Returns the verification key for the given kid
24+
# @param [String] kid the key id
1825
def key_for(kid)
1926
raise ::JWT::DecodeError, 'No key id (kid) found from token headers' unless kid || @allow_nil_kid
2027
raise ::JWT::DecodeError, 'Invalid type for kid header parameter' unless kid.nil? || kid.is_a?(String)
@@ -27,6 +34,12 @@ def key_for(kid)
2734
jwk.verify_key
2835
end
2936

37+
# Returns the key for the given token
38+
# @param [JWT::EncodedToken] token the token
39+
def call(token)
40+
key_for(token.header['kid'])
41+
end
42+
3043
private
3144

3245
def resolve_key(kid)

spec/jwt/encoded_token_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@
161161
expect(token.verify_signature!(algorithm: 'HS256', key: key)).to eq(nil)
162162
end
163163
end
164+
165+
context 'when JWT::KeyFinder is used as a key_finder' do
166+
let(:jwk) { JWT::JWK.new(test_pkey('rsa-2048-private.pem')) }
167+
let(:encoded_token) do
168+
JWT::Token.new(payload: payload, header: { kid: jwk.kid })
169+
.tap { |t| t.sign!(algorithm: 'RS256', key: jwk.signing_key) }
170+
.jwt
171+
end
172+
173+
it 'uses the keys provided by the JWK key finder' do
174+
key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
175+
expect(token.verify_signature!(algorithm: 'RS256', key_finder: key_finder)).to eq(nil)
176+
end
177+
end
164178
end
165179

166180
describe '#verify_claims!' do

0 commit comments

Comments
 (0)