jwt 3.0.0 → 3.1.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 +12 -0
- data/README.md +81 -93
- data/lib/jwt/decode.rb +8 -1
- data/lib/jwt/encoded_token.rb +51 -21
- data/lib/jwt/jwa/ecdsa.rb +25 -0
- data/lib/jwt/jwa.rb +51 -2
- data/lib/jwt/jwk/ec.rb +51 -57
- data/lib/jwt/jwk/key_base.rb +19 -0
- data/lib/jwt/jwk/key_finder.rb +22 -9
- data/lib/jwt/jwk/rsa.rb +1 -0
- data/lib/jwt/token.rb +5 -5
- data/lib/jwt/version.rb +1 -1
- data/ruby-jwt.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9dd430911612a1bef370bffdf46f432a9ec2027f2091efd8d47e6d237d8c935
|
4
|
+
data.tar.gz: 6fa7001bc60edeb8c984cddef3bf5d9beb42656555593ad8ffc2dfe3ef4b76e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11a6a56acb50a86223e7f60f413ac723c34997bd7ad5f2bbf2376e6c66fbfca1991a08301147062e7d5835f8fd530509b53a80876781d890d52e7fab7389fe18
|
7
|
+
data.tar.gz: 44f001cb3c746e4fe502a72fa9bc9234066a687145443c14cd4b09ef2bd184d119bf0d99accbfd3cd748e0eb1e8696fd90446cc3be4851ead641adbba9246b7c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.1.0](https://github.com/jwt/ruby-jwt/tree/v3.1.0) (2025-06-23)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.0.0...v3.1.0)
|
6
|
+
|
7
|
+
**Features:**
|
8
|
+
|
9
|
+
- Add support for x5t header parameter for X.509 certificate thumbprint verification [#669](https://github.com/jwt/ruby-jwt/pull/669) ([@hieuk09](https://github.com/hieuk09))
|
10
|
+
- Raise an error if the ECDSA signing or verification key is not an instance of `OpenSSL::PKey::EC` [#688](https://github.com/jwt/ruby-jwt/pull/688) ([@anakinj](https://github.com/anakinj))
|
11
|
+
- Allow `OpenSSL::PKey::EC::Point` to be used as the verification key in ECDSA [#689](https://github.com/jwt/ruby-jwt/pull/689) ([@anakinj](https://github.com/anakinj))
|
12
|
+
- Require claims to have been verified before accessing the `JWT::EncodedToken#payload` [#690](https://github.com/jwt/ruby-jwt/pull/690) ([@anakinj](https://github.com/anakinj))
|
13
|
+
- Support signing and verifying tokens using a JWK [#692](https://github.com/jwt/ruby-jwt/pull/692) ([@anakinj](https://github.com/anakinj))
|
14
|
+
|
3
15
|
## [v3.0.0](https://github.com/jwt/ruby-jwt/tree/v3.0.0) (2025-06-14)
|
4
16
|
|
5
17
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.10.1...v3.0.0)
|
data/README.md
CHANGED
@@ -9,11 +9,7 @@ A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools
|
|
9
9
|
|
10
10
|
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
11
11
|
|
12
|
-
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
13
|
-
|
14
|
-
## Upcoming breaking changes
|
15
|
-
|
16
|
-
Check out breaking changes in the upcoming **version 3.0** from the [upgrade guide](UPGRADING.md)
|
12
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes and [upgrade guide](UPGRADING.md) for upgrading between major versions.
|
17
13
|
|
18
14
|
## Sponsors
|
19
15
|
|
@@ -60,24 +56,15 @@ See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values fo
|
|
60
56
|
- none - unsigned token
|
61
57
|
|
62
58
|
```ruby
|
63
|
-
|
64
59
|
payload = { data: 'test' }
|
60
|
+
token = JWT.encode(payload, nil, 'none')
|
61
|
+
# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
|
65
62
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
# Set password to nil and validation to false otherwise this won't work
|
73
|
-
decoded_token = JWT.decode(token, nil, false)
|
74
|
-
|
75
|
-
# Array
|
76
|
-
# [
|
77
|
-
# {"data"=>"test"}, # payload
|
78
|
-
# {"alg"=>"none"} # header
|
79
|
-
# ]
|
80
|
-
puts decoded_token
|
63
|
+
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
|
64
|
+
# => [
|
65
|
+
# {"data"=>"test"}, # payload
|
66
|
+
# {"alg"=>"none"} # header
|
67
|
+
# ]
|
81
68
|
```
|
82
69
|
|
83
70
|
### **HMAC**
|
@@ -87,22 +74,17 @@ puts decoded_token
|
|
87
74
|
- HS512 - HMAC using SHA-512 hash algorithm
|
88
75
|
|
89
76
|
```ruby
|
90
|
-
|
77
|
+
payload = { data: 'test' }
|
91
78
|
hmac_secret = 'my$ecretK3y'
|
92
79
|
|
93
80
|
token = JWT.encode(payload, hmac_secret, 'HS256')
|
94
|
-
|
95
|
-
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
|
96
|
-
puts token
|
81
|
+
# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
|
97
82
|
|
98
83
|
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
99
|
-
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
# {"alg"=>"HS256"} # header
|
104
|
-
# ]
|
105
|
-
puts decoded_token
|
84
|
+
# => [
|
85
|
+
# {"data"=>"test"}, # payload
|
86
|
+
# {"alg"=>"HS256"} # header
|
87
|
+
# ]
|
106
88
|
```
|
107
89
|
|
108
90
|
### **RSA**
|
@@ -112,22 +94,18 @@ puts decoded_token
|
|
112
94
|
- RS512 - RSA using SHA-512 hash algorithm
|
113
95
|
|
114
96
|
```ruby
|
97
|
+
payload = { data: 'test' }
|
115
98
|
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
116
|
-
rsa_public
|
99
|
+
rsa_public = rsa_private.public_key
|
117
100
|
|
118
101
|
token = JWT.encode(payload, rsa_private, 'RS256')
|
119
|
-
|
120
|
-
# eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
|
121
|
-
puts token
|
102
|
+
# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
|
122
103
|
|
123
104
|
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
|
124
|
-
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# {"alg"=>"RS256"} # header
|
129
|
-
# ]
|
130
|
-
puts decoded_token
|
105
|
+
# => [
|
106
|
+
# {"data"=>"test"}, # payload
|
107
|
+
# {"alg"=>"RS256"} # header
|
108
|
+
# ]
|
131
109
|
```
|
132
110
|
|
133
111
|
### **ECDSA**
|
@@ -138,26 +116,22 @@ puts decoded_token
|
|
138
116
|
- ES256K - ECDSA using P-256K and SHA-256
|
139
117
|
|
140
118
|
```ruby
|
119
|
+
payload = { data: 'test' }
|
141
120
|
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
142
121
|
|
143
122
|
token = JWT.encode(payload, ecdsa_key, 'ES256')
|
144
|
-
|
145
|
-
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
146
|
-
puts token
|
123
|
+
# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
|
147
124
|
|
148
125
|
decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
|
149
|
-
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
# {"alg"=>"ES256"} # header
|
154
|
-
# ]
|
155
|
-
puts decoded_token
|
126
|
+
# => [
|
127
|
+
# {"test"=>"data"}, # payload
|
128
|
+
# {"alg"=>"ES256"} # header
|
129
|
+
# ]
|
156
130
|
```
|
157
131
|
|
158
132
|
### **EdDSA**
|
159
133
|
|
160
|
-
|
134
|
+
Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
|
161
135
|
|
162
136
|
### **RSASSA-PSS**
|
163
137
|
|
@@ -166,22 +140,18 @@ This algorithm has since version 3.0 been moved to the [jwt-eddsa gem](https://r
|
|
166
140
|
- PS512 - RSASSA-PSS using SHA-512 hash algorithm
|
167
141
|
|
168
142
|
```ruby
|
143
|
+
payload = { data: 'test' }
|
169
144
|
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
170
|
-
rsa_public
|
145
|
+
rsa_public = rsa_private.public_key
|
171
146
|
|
172
147
|
token = JWT.encode(payload, rsa_private, 'PS256')
|
173
|
-
|
174
|
-
# eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
|
175
|
-
puts token
|
148
|
+
# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
|
176
149
|
|
177
150
|
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
|
178
|
-
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
# {"alg"=>"PS256"} # header
|
183
|
-
# ]
|
184
|
-
puts decoded_token
|
151
|
+
# => [
|
152
|
+
# {"data"=>"test"}, # payload
|
153
|
+
# {"alg"=>"PS256"} # header
|
154
|
+
# ]
|
185
155
|
```
|
186
156
|
|
187
157
|
### **Custom algorithms**
|
@@ -210,8 +180,15 @@ module CustomHS512Algorithm
|
|
210
180
|
end
|
211
181
|
end
|
212
182
|
|
213
|
-
|
214
|
-
|
183
|
+
payload = { data: 'test' }
|
184
|
+
token = JWT.encode(payload, 'secret', CustomHS512Algorithm)
|
185
|
+
# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
|
186
|
+
|
187
|
+
decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
|
188
|
+
# => [
|
189
|
+
# {"data"=>"test"}, # payload
|
190
|
+
# {"alg"=>"HS512"} # header
|
191
|
+
# ]
|
215
192
|
```
|
216
193
|
|
217
194
|
### Add custom header fields
|
@@ -220,30 +197,16 @@ The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc
|
|
220
197
|
To add custom header fields you need to pass `header_fields` parameter
|
221
198
|
|
222
199
|
```ruby
|
223
|
-
token = JWT.encode(payload, key, 'HS256', {})
|
224
|
-
```
|
225
|
-
|
226
|
-
**Example:**
|
227
|
-
|
228
|
-
```ruby
|
229
|
-
|
230
200
|
payload = { data: 'test' }
|
231
201
|
|
232
|
-
# IMPORTANT: set nil as password parameter
|
233
202
|
token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
|
203
|
+
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
|
234
204
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
#
|
239
|
-
|
240
|
-
|
241
|
-
# Array
|
242
|
-
# [
|
243
|
-
# {"data"=>"test"}, # payload
|
244
|
-
# {"typ"=>"JWT", "alg"=>"none"} # header
|
245
|
-
# ]
|
246
|
-
puts decoded_token
|
205
|
+
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
|
206
|
+
# => [
|
207
|
+
# {"data"=>"test"}, # payload
|
208
|
+
# {"typ"=>"JWT", "alg"=>"none"} # header
|
209
|
+
# ]
|
247
210
|
```
|
248
211
|
|
249
212
|
## `JWT::Token` and `JWT::EncodedToken`
|
@@ -253,10 +216,14 @@ The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs
|
|
253
216
|
### Signing and encoding a token
|
254
217
|
|
255
218
|
```ruby
|
256
|
-
|
219
|
+
payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }
|
220
|
+
header = { kid: 'hmac' }
|
221
|
+
|
222
|
+
token = JWT::Token.new(payload: payload, header: header)
|
257
223
|
token.sign!(algorithm: 'HS256', key: "secret")
|
258
224
|
|
259
|
-
token.jwt
|
225
|
+
token.jwt
|
226
|
+
# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
|
260
227
|
```
|
261
228
|
|
262
229
|
### Verifying and decoding a token
|
@@ -284,7 +251,27 @@ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
|
|
284
251
|
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
|
285
252
|
```
|
286
253
|
|
287
|
-
|
254
|
+
A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key.
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
jwk_json = '{
|
258
|
+
"kty": "oct",
|
259
|
+
"k": "c2VjcmV0",
|
260
|
+
"alg": "HS256",
|
261
|
+
"kid": "hmac"
|
262
|
+
}'
|
263
|
+
|
264
|
+
jwk = JWT::JWK.import(JSON.parse(jwk_json))
|
265
|
+
|
266
|
+
token = JWT::Token.new(payload: payload, header: header)
|
267
|
+
|
268
|
+
token.sign!(key: jwk)
|
269
|
+
|
270
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
271
|
+
encoded_token.verify!(signature: { key: jwk})
|
272
|
+
```
|
273
|
+
|
274
|
+
#### Using a keyfinder
|
288
275
|
|
289
276
|
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.
|
290
277
|
|
@@ -677,13 +664,14 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
|
|
677
664
|
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
|
678
665
|
```
|
679
666
|
|
680
|
-
The `jwks` option can also be given as a lambda that evaluates every time a
|
667
|
+
The `jwks` option can also be given as a lambda that evaluates every time a key identifier is resolved.
|
681
668
|
This can be used to implement caching of remotely fetched JWK Sets.
|
682
669
|
|
683
|
-
|
670
|
+
Key identifiers can be specified using `kid`, `x5t` header parameters.
|
671
|
+
If the requested identifier is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
|
684
672
|
The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
|
685
673
|
|
686
|
-
Tokens without a specified `kid` are rejected by default.
|
674
|
+
Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default.
|
687
675
|
This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
|
688
676
|
|
689
677
|
```ruby
|
data/lib/jwt/decode.rb
CHANGED
@@ -65,7 +65,14 @@ module JWT
|
|
65
65
|
|
66
66
|
def set_key
|
67
67
|
@key = find_key(&@keyfinder) if @keyfinder
|
68
|
-
|
68
|
+
if @options[:jwks]
|
69
|
+
@key = ::JWT::JWK::KeyFinder.new(
|
70
|
+
jwks: @options[:jwks],
|
71
|
+
allow_nil_kid: @options[:allow_nil_kid],
|
72
|
+
key_fields: @options[:key_fields]
|
73
|
+
).call(token)
|
74
|
+
end
|
75
|
+
|
69
76
|
return unless (x5c_options = @options[:x5c])
|
70
77
|
|
71
78
|
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c'])
|
data/lib/jwt/encoded_token.rb
CHANGED
@@ -28,6 +28,10 @@ module JWT
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
DEFAULT_CLAIMS = [:exp].freeze
|
32
|
+
|
33
|
+
private_constant(:DEFAULT_CLAIMS)
|
34
|
+
|
31
35
|
# Returns the original token provided to the class.
|
32
36
|
# @return [String] The JWT token.
|
33
37
|
attr_reader :jwt
|
@@ -41,6 +45,8 @@ module JWT
|
|
41
45
|
|
42
46
|
@jwt = jwt
|
43
47
|
@signature_verified = false
|
48
|
+
@claims_verified = false
|
49
|
+
|
44
50
|
@encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
|
45
51
|
end
|
46
52
|
|
@@ -68,12 +74,13 @@ module JWT
|
|
68
74
|
# @return [String] the encoded header.
|
69
75
|
attr_reader :encoded_header
|
70
76
|
|
71
|
-
# Returns the payload of the JWT token. Access requires the signature to have been verified.
|
77
|
+
# Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
|
72
78
|
#
|
73
79
|
# @return [Hash] the payload.
|
74
80
|
# @raise [JWT::DecodeError] if the signature has not been verified.
|
75
81
|
def payload
|
76
82
|
raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified
|
83
|
+
raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified
|
77
84
|
|
78
85
|
decoded_payload
|
79
86
|
end
|
@@ -106,12 +113,23 @@ module JWT
|
|
106
113
|
# @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
|
107
114
|
# @return [nil]
|
108
115
|
# @raise [JWT::DecodeError] if the signature or claim verification fails.
|
109
|
-
def verify!(signature:, claims:
|
116
|
+
def verify!(signature:, claims: nil)
|
110
117
|
verify_signature!(**signature)
|
111
118
|
claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
|
112
119
|
nil
|
113
120
|
end
|
114
121
|
|
122
|
+
# Verifies the token signature and claims.
|
123
|
+
# By default it verifies the 'exp' claim.
|
124
|
+
|
125
|
+
# @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
|
126
|
+
# @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
|
127
|
+
# @return [Boolean] true if the signature and claims are valid, false otherwise.
|
128
|
+
def valid?(signature:, claims: nil)
|
129
|
+
valid_signature?(**signature) &&
|
130
|
+
(claims.is_a?(Array) ? valid_claims?(*claims) : valid_claims?(claims))
|
131
|
+
end
|
132
|
+
|
115
133
|
# Verifies the signature of the JWT token.
|
116
134
|
#
|
117
135
|
# @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
|
@@ -120,12 +138,8 @@ module JWT
|
|
120
138
|
# @return [nil]
|
121
139
|
# @raise [JWT::VerificationError] if the signature verification fails.
|
122
140
|
# @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided.
|
123
|
-
def verify_signature!(algorithm
|
124
|
-
|
125
|
-
|
126
|
-
key ||= key_finder.call(self)
|
127
|
-
|
128
|
-
return if valid_signature?(algorithm: algorithm, key: key)
|
141
|
+
def verify_signature!(algorithm: nil, key: nil, key_finder: nil)
|
142
|
+
return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder)
|
129
143
|
|
130
144
|
raise JWT::VerificationError, 'Signature verification failed'
|
131
145
|
end
|
@@ -133,43 +147,59 @@ module JWT
|
|
133
147
|
# Checks if the signature of the JWT token is valid.
|
134
148
|
#
|
135
149
|
# @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
|
136
|
-
# @param key [String, Array<String>] the key(s) to use for verification.
|
150
|
+
# @param key [String, Array<String>, JWT::JWK::KeyBase, Array<JWT::JWK::KeyBase>] the key(s) to use for verification.
|
151
|
+
# @param key_finder [#call] an object responding to `call` to find the key for verification.
|
137
152
|
# @return [Boolean] true if the signature is valid, false otherwise.
|
138
|
-
def valid_signature?(algorithm
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
153
|
+
def valid_signature?(algorithm: nil, key: nil, key_finder: nil)
|
154
|
+
raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil?
|
155
|
+
|
156
|
+
keys = Array(key || key_finder.call(self))
|
157
|
+
verifiers = JWA.create_verifiers(algorithms: algorithm, keys: keys, preferred_algorithm: header['alg'])
|
144
158
|
|
159
|
+
raise JWT::VerificationError, 'No algorithm provided' if verifiers.empty?
|
160
|
+
|
161
|
+
valid = verifiers.any? do |jwa|
|
162
|
+
jwa.verify(data: signing_input, signature: signature)
|
163
|
+
end
|
145
164
|
valid.tap { |verified| @signature_verified = verified }
|
146
165
|
end
|
147
166
|
|
148
167
|
# Verifies the claims of the token.
|
149
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
168
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
150
169
|
# @raise [JWT::DecodeError] if the claims are invalid.
|
151
170
|
def verify_claims!(*options)
|
152
|
-
Claims::Verifier.verify!(ClaimsContext.new(self), *options)
|
171
|
+
Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do
|
172
|
+
@claims_verified = true
|
173
|
+
end
|
174
|
+
rescue StandardError
|
175
|
+
@claims_verified = false
|
176
|
+
raise
|
153
177
|
end
|
154
178
|
|
155
179
|
# Returns the errors of the claims of the token.
|
156
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
180
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
157
181
|
# @return [Array<Symbol>] the errors of the claims.
|
158
182
|
def claim_errors(*options)
|
159
|
-
Claims::Verifier.errors(ClaimsContext.new(self), *options)
|
183
|
+
Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options))
|
160
184
|
end
|
161
185
|
|
162
186
|
# Returns whether the claims of the token are valid.
|
163
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
187
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
164
188
|
# @return [Boolean] whether the claims are valid.
|
165
189
|
def valid_claims?(*options)
|
166
|
-
claim_errors(*options).empty
|
190
|
+
claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified }
|
167
191
|
end
|
168
192
|
|
169
193
|
alias to_s jwt
|
170
194
|
|
171
195
|
private
|
172
196
|
|
197
|
+
def claims_options(options)
|
198
|
+
return DEFAULT_CLAIMS if options.first.nil?
|
199
|
+
|
200
|
+
options
|
201
|
+
end
|
202
|
+
|
173
203
|
def decode_payload
|
174
204
|
raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''
|
175
205
|
|
data/lib/jwt/jwa/ecdsa.rb
CHANGED
@@ -12,14 +12,22 @@ module JWT
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def sign(data:, signing_key:)
|
15
|
+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC)
|
16
|
+
raise_sign_error!('The given key is not a private key') unless signing_key.private?
|
17
|
+
|
15
18
|
curve_definition = curve_by_name(signing_key.group.curve_name)
|
16
19
|
key_algorithm = curve_definition[:algorithm]
|
20
|
+
|
17
21
|
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
|
18
22
|
|
19
23
|
asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
|
20
24
|
end
|
21
25
|
|
22
26
|
def verify(data:, signature:, verification_key:)
|
27
|
+
verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point)
|
28
|
+
|
29
|
+
raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC)
|
30
|
+
|
23
31
|
curve_definition = curve_by_name(verification_key.group.curve_name)
|
24
32
|
key_algorithm = curve_definition[:algorithm]
|
25
33
|
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
|
@@ -56,12 +64,29 @@ module JWT
|
|
56
64
|
register_algorithm(new(v[:algorithm], v[:digest]))
|
57
65
|
end
|
58
66
|
|
67
|
+
# @api private
|
59
68
|
def self.curve_by_name(name)
|
60
69
|
NAMED_CURVES.fetch(name) do
|
61
70
|
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
74
|
+
if ::JWT.openssl_3?
|
75
|
+
def self.create_public_key_from_point(point)
|
76
|
+
sequence = OpenSSL::ASN1::Sequence([
|
77
|
+
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]),
|
78
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
79
|
+
])
|
80
|
+
OpenSSL::PKey::EC.new(sequence.to_der)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
def self.create_public_key_from_point(point)
|
84
|
+
OpenSSL::PKey::EC.new(point.group.curve_name).tap do |key|
|
85
|
+
key.public_key = point
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
65
90
|
private
|
66
91
|
|
67
92
|
attr_reader :digest
|
data/lib/jwt/jwa.rb
CHANGED
@@ -13,11 +13,43 @@ require_relative 'jwa/unsupported'
|
|
13
13
|
module JWT
|
14
14
|
# The JWA module contains all supported algorithms.
|
15
15
|
module JWA
|
16
|
+
# @api private
|
17
|
+
class VerifierContext
|
18
|
+
def initialize(jwa:, keys:)
|
19
|
+
@jwa = jwa
|
20
|
+
@keys = Array(keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(*args, **kwargs)
|
24
|
+
@keys.any? do |key|
|
25
|
+
@jwa.verify(*args, **kwargs, verification_key: key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
class SignerContext
|
32
|
+
def initialize(jwa:, key:)
|
33
|
+
@jwa = jwa
|
34
|
+
@key = key
|
35
|
+
end
|
36
|
+
|
37
|
+
def sign(*args, **kwargs)
|
38
|
+
@jwa.sign(*args, **kwargs, signing_key: @key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def jwa_header
|
42
|
+
@jwa.header
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
16
46
|
class << self
|
17
47
|
# @api private
|
18
48
|
def resolve(algorithm)
|
19
49
|
return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
|
20
50
|
|
51
|
+
raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
|
52
|
+
|
21
53
|
raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
|
22
54
|
|
23
55
|
algorithm
|
@@ -25,8 +57,25 @@ module JWT
|
|
25
57
|
|
26
58
|
# @api private
|
27
59
|
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
28
|
-
|
29
|
-
|
60
|
+
Array(algorithms).map { |alg| JWA.resolve(alg) }
|
61
|
+
.partition { |alg| alg.valid_alg?(preferred_algorithm) }
|
62
|
+
.flatten
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
def create_signer(algorithm:, key:)
|
67
|
+
return key if key.is_a?(JWK::KeyBase)
|
68
|
+
|
69
|
+
SignerContext.new(jwa: resolve(algorithm), key: key)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def create_verifiers(algorithms:, keys:, preferred_algorithm:)
|
74
|
+
jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
|
75
|
+
|
76
|
+
jwks + resolve_and_sort(algorithms: algorithms,
|
77
|
+
preferred_algorithm: preferred_algorithm)
|
78
|
+
.map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
|
30
79
|
end
|
31
80
|
end
|
32
81
|
end
|
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -73,6 +73,13 @@ module JWT
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
+
def jwa
|
77
|
+
return super if self[:alg]
|
78
|
+
|
79
|
+
curve_name = self.class.to_openssl_curve(self[:crv])
|
80
|
+
JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
|
81
|
+
end
|
82
|
+
|
76
83
|
def ec_key
|
77
84
|
@ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
|
78
85
|
end
|
@@ -136,67 +143,54 @@ module JWT
|
|
136
143
|
}.compact
|
137
144
|
end
|
138
145
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
# }
|
158
|
-
|
159
|
-
OpenSSL::ASN1::Sequence([
|
160
|
-
OpenSSL::ASN1::Integer(1),
|
161
|
-
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
162
|
-
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
163
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
164
|
-
])
|
165
|
-
else
|
166
|
-
OpenSSL::ASN1::Sequence([
|
167
|
-
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
168
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
169
|
-
])
|
170
|
-
end
|
146
|
+
def create_point(jwk_crv, jwk_x, jwk_y)
|
147
|
+
curve = EC.to_openssl_curve(jwk_crv)
|
148
|
+
x_octets = decode_octets(jwk_x)
|
149
|
+
y_octets = decode_octets(jwk_y)
|
150
|
+
|
151
|
+
# The details of the `Point` instantiation are covered in:
|
152
|
+
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
153
|
+
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
154
|
+
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
155
|
+
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
156
|
+
# Section 2.3.3 of the last of these references specifies that the
|
157
|
+
# encoding of an uncompressed point consists of the byte `0x04` followed
|
158
|
+
# by the x value then the y value.
|
159
|
+
OpenSSL::PKey::EC::Point.new(
|
160
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
161
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
162
|
+
)
|
163
|
+
end
|
171
164
|
|
165
|
+
if ::JWT.openssl_3?
|
166
|
+
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
167
|
+
point = create_point(jwk_crv, jwk_x, jwk_y)
|
168
|
+
|
169
|
+
return ::JWT::JWA::Ecdsa.create_public_key_from_point(point) unless jwk_d
|
170
|
+
|
171
|
+
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
172
|
+
# ECPrivateKey ::= SEQUENCE {
|
173
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
174
|
+
# privateKey OCTET STRING,
|
175
|
+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
176
|
+
# publicKey [1] BIT STRING OPTIONAL
|
177
|
+
# }
|
178
|
+
|
179
|
+
sequence = OpenSSL::ASN1::Sequence([
|
180
|
+
OpenSSL::ASN1::Integer(1),
|
181
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
182
|
+
OpenSSL::ASN1::ObjectId(point.group.curve_name, 0, :EXPLICIT),
|
183
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
184
|
+
])
|
172
185
|
OpenSSL::PKey::EC.new(sequence.to_der)
|
173
186
|
end
|
174
187
|
else
|
175
188
|
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
key = OpenSSL::PKey::EC.new(curve)
|
182
|
-
|
183
|
-
# The details of the `Point` instantiation are covered in:
|
184
|
-
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
185
|
-
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
186
|
-
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
187
|
-
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
188
|
-
# Section 2.3.3 of the last of these references specifies that the
|
189
|
-
# encoding of an uncompressed point consists of the byte `0x04` followed
|
190
|
-
# by the x value then the y value.
|
191
|
-
point = OpenSSL::PKey::EC::Point.new(
|
192
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
193
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
194
|
-
)
|
195
|
-
|
196
|
-
key.public_key = point
|
197
|
-
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
198
|
-
|
199
|
-
key
|
189
|
+
point = create_point(jwk_crv, jwk_x, jwk_y)
|
190
|
+
|
191
|
+
::JWT::JWA::Ecdsa.create_public_key_from_point(point).tap do |key|
|
192
|
+
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
193
|
+
end
|
200
194
|
end
|
201
195
|
end
|
202
196
|
|
@@ -205,7 +199,7 @@ module JWT
|
|
205
199
|
# Some base64 encoders on some platform omit a single 0-byte at
|
206
200
|
# the start of either Y or X coordinate of the elliptic curve point.
|
207
201
|
# This leads to an encoding error when data is passed to OpenSSL BN.
|
208
|
-
# It is know to have
|
202
|
+
# It is know to have happened to exported JWKs on a Java application and
|
209
203
|
# on a Flutter/Dart application (both iOS and Android). All that is
|
210
204
|
# needed to fix the problem is adding a leading 0-byte. We know the
|
211
205
|
# required byte is 0 because with any other byte the point is no longer
|
data/lib/jwt/jwk/key_base.rb
CHANGED
@@ -42,6 +42,19 @@ module JWT
|
|
42
42
|
other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
|
43
43
|
end
|
44
44
|
|
45
|
+
def verify(**kwargs)
|
46
|
+
jwa.verify(**kwargs, verification_key: verify_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def sign(**kwargs)
|
50
|
+
jwa.sign(**kwargs, signing_key: signing_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def jwa_header
|
55
|
+
jwa.header
|
56
|
+
end
|
57
|
+
|
45
58
|
alias eql? ==
|
46
59
|
|
47
60
|
def <=>(other)
|
@@ -52,6 +65,12 @@ module JWT
|
|
52
65
|
|
53
66
|
private
|
54
67
|
|
68
|
+
def jwa
|
69
|
+
raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg]
|
70
|
+
|
71
|
+
JWA.resolve(self[:alg])
|
72
|
+
end
|
73
|
+
|
55
74
|
attr_reader :parameters
|
56
75
|
end
|
57
76
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
@@ -9,6 +9,9 @@ module JWT
|
|
9
9
|
# @param [Hash] options the options to create a KeyFinder with
|
10
10
|
# @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc
|
11
11
|
# @option options [Boolean] :allow_nil_kid whether to allow nil kid
|
12
|
+
# @option options [Array] :key_fields the fields to use for key matching,
|
13
|
+
# the order of the fields are used to determine
|
14
|
+
# the priority of the keys.
|
12
15
|
def initialize(options)
|
13
16
|
@allow_nil_kid = options[:allow_nil_kid]
|
14
17
|
jwks_or_loader = options[:jwks]
|
@@ -18,15 +21,16 @@ module JWT
|
|
18
21
|
else
|
19
22
|
->(_options) { jwks_or_loader }
|
20
23
|
end
|
24
|
+
|
25
|
+
@key_fields = options[:key_fields] || %i[kid]
|
21
26
|
end
|
22
27
|
|
23
28
|
# Returns the verification key for the given kid
|
24
29
|
# @param [String] kid the key id
|
25
|
-
def key_for(kid)
|
26
|
-
raise ::JWT::DecodeError,
|
27
|
-
raise ::JWT::DecodeError, 'Invalid type for kid header parameter' unless kid.nil? || kid.is_a?(String)
|
30
|
+
def key_for(kid, key_field = :kid)
|
31
|
+
raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
|
28
32
|
|
29
|
-
jwk = resolve_key(kid)
|
33
|
+
jwk = resolve_key(kid, key_field)
|
30
34
|
|
31
35
|
raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
|
32
36
|
raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
|
@@ -37,22 +41,31 @@ module JWT
|
|
37
41
|
# Returns the key for the given token
|
38
42
|
# @param [JWT::EncodedToken] token the token
|
39
43
|
def call(token)
|
40
|
-
|
44
|
+
@key_fields.each do |key_field|
|
45
|
+
field_value = token.header[key_field.to_s]
|
46
|
+
|
47
|
+
return key_for(field_value, key_field) if field_value
|
48
|
+
end
|
49
|
+
|
50
|
+
raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
|
51
|
+
|
52
|
+
kid = token.header['kid']
|
53
|
+
key_for(kid)
|
41
54
|
end
|
42
55
|
|
43
56
|
private
|
44
57
|
|
45
|
-
def resolve_key(kid)
|
46
|
-
key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[
|
58
|
+
def resolve_key(kid, key_field)
|
59
|
+
key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[key_field] == kid }
|
47
60
|
|
48
61
|
# First try without invalidation to facilitate application caching
|
49
|
-
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(
|
62
|
+
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid))
|
50
63
|
jwk = @jwks.find { |key| key_matcher.call(key) }
|
51
64
|
|
52
65
|
return jwk if jwk
|
53
66
|
|
54
67
|
# Second try, invalidate for backwards compatibility
|
55
|
-
@jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true,
|
68
|
+
@jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, key_field => kid))
|
56
69
|
@jwks.find { |key| key_matcher.call(key) }
|
57
70
|
end
|
58
71
|
end
|
data/lib/jwt/jwk/rsa.rb
CHANGED
data/lib/jwt/token.rb
CHANGED
@@ -87,16 +87,16 @@ module JWT
|
|
87
87
|
|
88
88
|
# Signs the JWT token.
|
89
89
|
#
|
90
|
+
# @param key [String, JWT::JWK::KeyBase] the key to use for signing.
|
90
91
|
# @param algorithm [String, Object] the algorithm to use for signing.
|
91
|
-
# @param key [String] the key to use for signing.
|
92
92
|
# @return [void]
|
93
93
|
# @raise [JWT::EncodeError] if the token is already signed or other problems when signing
|
94
|
-
def sign!(
|
94
|
+
def sign!(key:, algorithm: nil)
|
95
95
|
raise ::JWT::EncodeError, 'Token already signed' if @signature
|
96
96
|
|
97
|
-
JWA.
|
98
|
-
header.merge!(
|
99
|
-
@signature =
|
97
|
+
JWA.create_signer(algorithm: algorithm, key: key).tap do |signer|
|
98
|
+
header.merge!(signer.jwa_header) { |_key, old, _new| old }
|
99
|
+
@signature = signer.sign(data: signing_input)
|
100
100
|
end
|
101
101
|
|
102
102
|
nil
|
data/lib/jwt/version.rb
CHANGED
data/ruby-jwt.gemspec
CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
|
36
36
|
spec.add_development_dependency 'appraisal'
|
37
37
|
spec.add_development_dependency 'bundler'
|
38
|
+
spec.add_development_dependency 'irb'
|
38
39
|
spec.add_development_dependency 'logger'
|
39
40
|
spec.add_development_dependency 'rake'
|
40
41
|
spec.add_development_dependency 'rspec'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Rudat
|
@@ -51,6 +51,20 @@ dependencies:
|
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: irb
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
54
68
|
- !ruby/object:Gem::Dependency
|
55
69
|
name: logger
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,7 +199,7 @@ licenses:
|
|
185
199
|
- MIT
|
186
200
|
metadata:
|
187
201
|
bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
|
188
|
-
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.
|
202
|
+
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.1.0/CHANGELOG.md
|
189
203
|
rubygems_mfa_required: 'true'
|
190
204
|
rdoc_options: []
|
191
205
|
require_paths:
|