jwt 3.0.0.beta1 → 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.
data/README.md CHANGED
@@ -2,44 +2,41 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
4
4
  [![Build Status](https://github.com/jwt/ruby-jwt/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
5
- [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
6
- [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
7
- [![Issue Count](https://codeclimate.com/github/jwt/ruby-jwt/badges/issue_count.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
5
+ [![Maintainability](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/maintainability.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt)
6
+ [![Code Coverage](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/test_coverage.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt)
8
7
 
9
8
  A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
10
9
 
11
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).
12
11
 
13
- See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
14
-
15
- ## Upcoming breaking changes
16
-
17
- 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.
18
13
 
19
14
  ## Sponsors
20
15
 
21
- |Logo|Message|
22
- |-|-|
23
- |![auth0 logo](https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png)|If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth)|
16
+ | Logo | Message |
17
+ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
18
+ | ![auth0 logo](https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png) | If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth) |
24
19
 
25
20
  ## Installing
26
21
 
27
- ### Using Rubygems:
22
+ ### Using Rubygems
28
23
 
29
24
  ```bash
30
25
  gem install jwt
31
26
  ```
32
27
 
33
- ### Using Bundler:
28
+ ### Using Bundler
34
29
 
35
30
  Add the following to your Gemfile
36
- ```
31
+
32
+ ```bash
37
33
  gem 'jwt'
38
34
  ```
39
35
 
40
36
  And run `bundle install`
41
37
 
42
38
  Finally require the gem in your application
39
+
43
40
  ```ruby
44
41
  require 'jwt'
45
42
  ```
@@ -48,187 +45,113 @@ require 'jwt'
48
45
 
49
46
  The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions.
50
47
 
51
- Additionally the EdDSA algorithm is supported via a [separate gem](https://rubygems.org/gems/jwt-eddsa).
48
+ Additionally the EdDSA algorithm is supported via a the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
52
49
 
53
50
  For safe cryptographic signing, you need to specify the algorithm in the options hash whenever you call `JWT.decode` to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm**
54
51
 
55
- See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
56
-
57
- ### Deprecation warnings
58
-
59
- Deprecation warnings are logged once (`:once` option) by default to avoid spam in logs. Other options are `:silent` to completely silence warnings and `:warn` to log every time a deprecated path is executed.
60
-
61
- ```ruby
62
- JWT.configuration.deprecation_warnings = :warn # default is :once
63
- ```
64
-
65
- ### Base64 decoding
66
-
67
- In the past the gem has been supporting the Base64 decoding specified in [RFC2045](https://www.rfc-editor.org/rfc/rfc2045) allowing newlines and blanks in the base64 encoded payload. In future versions base64 decoding will be stricter and only comply to [RFC4648](https://www.rfc-editor.org/rfc/rfc4648).
68
-
69
- The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
70
- ```ruby
71
- JWT.configuration.strict_base64_decoding = true # default is false
72
- ```
52
+ See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
73
53
 
74
54
  ### **NONE**
75
55
 
76
- * none - unsigned token
56
+ - none - unsigned token
77
57
 
78
58
  ```ruby
79
-
80
59
  payload = { data: 'test' }
60
+ token = JWT.encode(payload, nil, 'none')
61
+ # => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
81
62
 
82
- # IMPORTANT: set nil as password parameter
83
- token = JWT.encode(payload, nil, 'none')
84
-
85
- # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
86
- puts token
87
-
88
- # Set password to nil and validation to false otherwise this won't work
89
- decoded_token = JWT.decode(token, nil, false)
90
-
91
- # Array
92
- # [
93
- # {"data"=>"test"}, # payload
94
- # {"alg"=>"none"} # header
95
- # ]
96
- puts decoded_token
63
+ decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
64
+ # => [
65
+ # {"data"=>"test"}, # payload
66
+ # {"alg"=>"none"} # header
67
+ # ]
97
68
  ```
98
69
 
99
70
  ### **HMAC**
100
71
 
101
- * HS256 - HMAC using SHA-256 hash algorithm
102
- * HS384 - HMAC using SHA-384 hash algorithm
103
- * HS512 - HMAC using SHA-512 hash algorithm
72
+ - HS256 - HMAC using SHA-256 hash algorithm
73
+ - HS384 - HMAC using SHA-384 hash algorithm
74
+ - HS512 - HMAC using SHA-512 hash algorithm
104
75
 
105
76
  ```ruby
106
- # The secret must be a string. With OpenSSL 3.0/openssl gem `<3.0.1`, JWT::DecodeError will be raised if it isn't provided.
77
+ payload = { data: 'test' }
107
78
  hmac_secret = 'my$ecretK3y'
108
79
 
109
80
  token = JWT.encode(payload, hmac_secret, 'HS256')
110
-
111
- # eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
112
- puts token
81
+ # => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
113
82
 
114
83
  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
115
-
116
- # Array
117
- # [
118
- # {"data"=>"test"}, # payload
119
- # {"alg"=>"HS256"} # header
120
- # ]
121
- puts decoded_token
84
+ # => [
85
+ # {"data"=>"test"}, # payload
86
+ # {"alg"=>"HS256"} # header
87
+ # ]
122
88
  ```
123
89
 
124
90
  ### **RSA**
125
91
 
126
- * RS256 - RSA using SHA-256 hash algorithm
127
- * RS384 - RSA using SHA-384 hash algorithm
128
- * RS512 - RSA using SHA-512 hash algorithm
92
+ - RS256 - RSA using SHA-256 hash algorithm
93
+ - RS384 - RSA using SHA-384 hash algorithm
94
+ - RS512 - RSA using SHA-512 hash algorithm
129
95
 
130
96
  ```ruby
97
+ payload = { data: 'test' }
131
98
  rsa_private = OpenSSL::PKey::RSA.generate(2048)
132
- rsa_public = rsa_private.public_key
99
+ rsa_public = rsa_private.public_key
133
100
 
134
101
  token = JWT.encode(payload, rsa_private, 'RS256')
135
-
136
- # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
137
- puts token
102
+ # => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
138
103
 
139
104
  decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
140
-
141
- # Array
142
- # [
143
- # {"data"=>"test"}, # payload
144
- # {"alg"=>"RS256"} # header
145
- # ]
146
- puts decoded_token
105
+ # => [
106
+ # {"data"=>"test"}, # payload
107
+ # {"alg"=>"RS256"} # header
108
+ # ]
147
109
  ```
148
110
 
149
111
  ### **ECDSA**
150
112
 
151
- * ES256 - ECDSA using P-256 and SHA-256
152
- * ES384 - ECDSA using P-384 and SHA-384
153
- * ES512 - ECDSA using P-521 and SHA-512
154
- * ES256K - ECDSA using P-256K and SHA-256
113
+ - ES256 - ECDSA using P-256 and SHA-256
114
+ - ES384 - ECDSA using P-384 and SHA-384
115
+ - ES512 - ECDSA using P-521 and SHA-512
116
+ - ES256K - ECDSA using P-256K and SHA-256
155
117
 
156
118
  ```ruby
119
+ payload = { data: 'test' }
157
120
  ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
158
121
 
159
122
  token = JWT.encode(payload, ecdsa_key, 'ES256')
160
-
161
- # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
162
- puts token
123
+ # => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
163
124
 
164
125
  decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
165
-
166
- # Array
167
- # [
168
- # {"test"=>"data"}, # payload
169
- # {"alg"=>"ES256"} # header
170
- # ]
171
- puts decoded_token
126
+ # => [
127
+ # {"test"=>"data"}, # payload
128
+ # {"alg"=>"ES256"} # header
129
+ # ]
172
130
  ```
173
131
 
174
132
  ### **EdDSA**
175
133
 
176
- This algorithm has since version 3.0 been moved to the [jwt-eddsa](https://rubygems.org/gems/jwt-eddsa) gem.
134
+ Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
177
135
 
178
136
  ### **RSASSA-PSS**
179
137
 
180
- * PS256 - RSASSA-PSS using SHA-256 hash algorithm
181
- * PS384 - RSASSA-PSS using SHA-384 hash algorithm
182
- * PS512 - RSASSA-PSS using SHA-512 hash algorithm
138
+ - PS256 - RSASSA-PSS using SHA-256 hash algorithm
139
+ - PS384 - RSASSA-PSS using SHA-384 hash algorithm
140
+ - PS512 - RSASSA-PSS using SHA-512 hash algorithm
183
141
 
184
142
  ```ruby
143
+ payload = { data: 'test' }
185
144
  rsa_private = OpenSSL::PKey::RSA.generate(2048)
186
- rsa_public = rsa_private.public_key
145
+ rsa_public = rsa_private.public_key
187
146
 
188
147
  token = JWT.encode(payload, rsa_private, 'PS256')
189
-
190
- # eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
191
- puts token
148
+ # => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
192
149
 
193
150
  decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
194
-
195
- # Array
196
- # [
197
- # {"data"=>"test"}, # payload
198
- # {"alg"=>"PS256"} # header
199
- # ]
200
- puts decoded_token
201
- ```
202
-
203
- ### Add custom header fields
204
- Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
205
- To add custom header fields you need to pass `header_fields` parameter
206
-
207
- ```ruby
208
- token = JWT.encode(payload, key, 'HS256', header_fields={})
209
- ```
210
-
211
- **Example:**
212
-
213
- ```ruby
214
-
215
- payload = { data: 'test' }
216
-
217
- # IMPORTANT: set nil as password parameter
218
- token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
219
-
220
- # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
221
- puts token
222
-
223
- # Set password to nil and validation to false otherwise this won't work
224
- decoded_token = JWT.decode(token, nil, false)
225
-
226
- # Array
227
- # [
228
- # {"data"=>"test"}, # payload
229
- # {"typ"=>"JWT", "alg"=>"none"} # header
230
- # ]
231
- puts decoded_token
151
+ # => [
152
+ # {"data"=>"test"}, # payload
153
+ # {"alg"=>"PS256"} # header
154
+ # ]
232
155
  ```
233
156
 
234
157
  ### **Custom algorithms**
@@ -240,7 +163,6 @@ When encoding or decoding a token, you can pass in a custom object through the `
240
163
 
241
164
  For customization options check the details from `JWT::JWA::SigningAlgorithm`.
242
165
 
243
-
244
166
  ```ruby
245
167
  module CustomHS512Algorithm
246
168
  extend JWT::JWA::SigningAlgorithm
@@ -258,22 +180,56 @@ module CustomHS512Algorithm
258
180
  end
259
181
  end
260
182
 
261
- token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
262
- payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
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
+ # ]
192
+ ```
193
+
194
+ ### Add custom header fields
195
+
196
+ The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
197
+ To add custom header fields you need to pass `header_fields` parameter
198
+
199
+ ```ruby
200
+ payload = { data: 'test' }
201
+
202
+ token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
203
+ # => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
204
+
205
+ decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
206
+ # => [
207
+ # {"data"=>"test"}, # payload
208
+ # {"typ"=>"JWT", "alg"=>"none"} # header
209
+ # ]
263
210
  ```
264
211
 
265
212
  ## `JWT::Token` and `JWT::EncodedToken`
266
213
 
267
214
  The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
268
215
 
216
+ ### Signing and encoding a token
217
+
269
218
  ```ruby
270
- token = JWT::Token.new(payload: { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }, header: { kid: 'hmac' })
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)
271
223
  token.sign!(algorithm: 'HS256', key: "secret")
272
224
 
273
- token.jwt # => "eyJhbGciOiJIUzI1N..."
225
+ token.jwt
226
+ # => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
274
227
  ```
275
228
 
276
- The `JWT::EncodedToken` can be used to create a token object that allows verification of signatures and claims
229
+ ### Verifying and decoding a token
230
+
231
+ The `JWT::EncodedToken` can be used as a token object that allows verification of signatures and claims.
232
+
277
233
  ```ruby
278
234
  encoded_token = JWT::EncodedToken.new(token.jwt)
279
235
 
@@ -291,14 +247,36 @@ The `JWT::EncodedToken#verify!` method can be used to verify signature and claim
291
247
  ```ruby
292
248
  encoded_token = JWT::EncodedToken.new(token.jwt)
293
249
  encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
294
-
295
250
  encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
296
251
  encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
297
252
  ```
298
253
 
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
275
+
299
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.
300
277
 
301
- An example on using the built-in JWK keyfinder:
278
+ An example on using the built-in JWK keyfinder.
279
+
302
280
  ```ruby
303
281
  # Create and sign a token
304
282
  jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
@@ -312,7 +290,8 @@ encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
312
290
  encoded_token.payload # => { 'pay' => 'load' }
313
291
  ```
314
292
 
315
- Using a custom keyfinder proc:
293
+ Using a custom keyfinder proc.
294
+
316
295
  ```ruby
317
296
  # Create and sign a token
318
297
  key = OpenSSL::PKey::RSA.generate(2048)
@@ -351,21 +330,19 @@ encoded_token.payload # => {"pay"=>"load"}
351
330
  JSON Web Token defines some reserved claim names and defines how they should be
352
331
  used. JWT supports these reserved claim names:
353
332
 
354
- - 'exp' (Expiration Time) Claim
355
- - 'nbf' (Not Before Time) Claim
356
- - 'iss' (Issuer) Claim
357
- - 'aud' (Audience) Claim
358
- - 'jti' (JWT ID) Claim
359
- - 'iat' (Issued At) Claim
360
- - 'sub' (Subject) Claim
333
+ - 'exp' (Expiration Time) Claim
334
+ - 'nbf' (Not Before Time) Claim
335
+ - 'iss' (Issuer) Claim
336
+ - 'aud' (Audience) Claim
337
+ - 'jti' (JWT ID) Claim
338
+ - 'iat' (Issued At) Claim
339
+ - 'sub' (Subject) Claim
361
340
 
362
341
  ### Expiration Time Claim
363
342
 
364
343
  From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
365
344
 
366
- > The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a ***NumericDate*** value. Use of this claim is OPTIONAL.
367
-
368
- **Handle Expiration Claim**
345
+ > The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
369
346
 
370
347
  ```ruby
371
348
  exp = Time.now.to_i + 4 * 3600
@@ -381,12 +358,13 @@ end
381
358
  ```
382
359
 
383
360
  The Expiration Claim verification can be disabled.
361
+
384
362
  ```ruby
385
363
  # Decode token without raising JWT::ExpiredSignature error
386
364
  JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
387
365
  ```
388
366
 
389
- **Adding Leeway**
367
+ Leeway and the exp claim.
390
368
 
391
369
  ```ruby
392
370
  exp = Time.now.to_i - 10
@@ -409,9 +387,7 @@ end
409
387
 
410
388
  From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5):
411
389
 
412
- > The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a ***NumericDate*** value. Use of this claim is OPTIONAL.
413
-
414
- **Handle Not Before Claim**
390
+ > The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
415
391
 
416
392
  ```ruby
417
393
  nbf = Time.now.to_i - 3600
@@ -427,12 +403,13 @@ end
427
403
  ```
428
404
 
429
405
  The Not Before Claim verification can be disabled.
406
+
430
407
  ```ruby
431
408
  # Decode token without raising JWT::ImmatureSignature error
432
409
  JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
433
410
  ```
434
411
 
435
- **Adding Leeway**
412
+ Leeway and the nbf claim.
436
413
 
437
414
  ```ruby
438
415
  nbf = Time.now.to_i + 10
@@ -455,7 +432,7 @@ end
455
432
 
456
433
  From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1):
457
434
 
458
- > The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a ***StringOrURI*** value. Use of this claim is OPTIONAL.
435
+ > The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL.
459
436
 
460
437
  You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
461
438
 
@@ -507,7 +484,7 @@ end
507
484
 
508
485
  From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
509
486
 
510
- > The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a ***StringOrURI*** value. In the special case when the JWT has one audience, the `aud` value MAY be a single case-sensitive string containing a ***StringOrURI*** value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
487
+ > The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a **_StringOrURI_** value. In the special case when the JWT has one audience, the `aud` value MAY be a single case-sensitive string containing a **_StringOrURI_** value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
511
488
 
512
489
  ```ruby
513
490
  aud = ['Young', 'Old']
@@ -555,9 +532,7 @@ end
555
532
 
556
533
  From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
557
534
 
558
- > The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a ***NumericDate*** value. Use of this claim is OPTIONAL.
559
-
560
- **Handle Issued At Claim**
535
+ > The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
561
536
 
562
537
  ```ruby
563
538
  iat = Time.now.to_i
@@ -577,7 +552,7 @@ end
577
552
 
578
553
  From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
579
554
 
580
- > The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a ***StringOrURI*** value. Use of this claim is OPTIONAL.
555
+ > The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL.
581
556
 
582
557
  ```ruby
583
558
  sub = 'Subject'
@@ -598,6 +573,7 @@ end
598
573
  The JWT claim verifications can be used to verify any Hash to include expected keys and values.
599
574
 
600
575
  A few example on verifying the claims for a payload:
576
+
601
577
  ```ruby
602
578
  JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
603
579
  JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
@@ -634,6 +610,7 @@ end
634
610
  ### Required Claims
635
611
 
636
612
  You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
613
+
637
614
  ```ruby
638
615
  # Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
639
616
  JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
@@ -687,13 +664,14 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
687
664
  JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
688
665
  ```
689
666
 
690
- The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
667
+ The `jwks` option can also be given as a lambda that evaluates every time a key identifier is resolved.
691
668
  This can be used to implement caching of remotely fetched JWK Sets.
692
669
 
693
- If the requested `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`.
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`.
694
672
  The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
695
673
 
696
- Tokens without a specified `kid` are rejected by default.
674
+ Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default.
697
675
  This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
698
676
 
699
677
  ```ruby
@@ -772,7 +750,7 @@ jwk_hash = jwk.export
772
750
  thumbprint_as_the_kid = jwk_hash[:kid]
773
751
  ```
774
752
 
775
- # Development and testing
753
+ ## Development and testing
776
754
 
777
755
  The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
778
756
 
@@ -781,7 +759,7 @@ bundle install
781
759
  bundle exec appraisal rake test
782
760
  ```
783
761
 
784
- # Releasing
762
+ ## Releasing
785
763
 
786
764
  To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:
787
765
 
@@ -792,6 +770,7 @@ rake release:source_control_push
792
770
  This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org.
793
771
 
794
772
  ## How to contribute
773
+
795
774
  See [CONTRIBUTING](CONTRIBUTING.md).
796
775
 
797
776
  ## Contributors
data/UPGRADING.md CHANGED
@@ -38,6 +38,7 @@ Claim verification has been [split into separate classes](https://github.com/jwt
38
38
  ## Algorithm restructuring
39
39
 
40
40
  The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes led to a few deprecations and new requirements:
41
+
41
42
  - The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
42
43
  - Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
43
44
 
data/lib/jwt/decode.rb CHANGED
@@ -6,6 +6,11 @@ require 'jwt/x5c_key_finder'
6
6
  module JWT
7
7
  # The Decode class is responsible for decoding and verifying JWT tokens.
8
8
  class Decode
9
+ # Order is very important - first check for string keys, next for symbols
10
+ ALGORITHM_KEYS = ['algorithm',
11
+ :algorithm,
12
+ 'algorithms',
13
+ :algorithms].freeze
9
14
  # Initializes a new Decode instance.
10
15
  #
11
16
  # @param jwt [String] the JWT to decode.
@@ -60,7 +65,14 @@ module JWT
60
65
 
61
66
  def set_key
62
67
  @key = find_key(&@keyfinder) if @keyfinder
63
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(token.header['kid']) if @options[:jwks]
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
+
64
76
  return unless (x5c_options = @options[:x5c])
65
77
 
66
78
  @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c'])
@@ -70,12 +82,6 @@ module JWT
70
82
  @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
71
83
  end
72
84
 
73
- # Order is very important - first check for string keys, next for symbols
74
- ALGORITHM_KEYS = ['algorithm',
75
- :algorithm,
76
- 'algorithms',
77
- :algorithms].freeze
78
-
79
85
  def given_algorithms
80
86
  alg_key = ALGORITHM_KEYS.find { |key| @options[key] }
81
87
  Array(@options[alg_key])