jwt 2.9.3 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -16
  3. data/README.md +153 -83
  4. data/lib/jwt/base64.rb +3 -0
  5. data/lib/jwt/claims/audience.rb +10 -0
  6. data/lib/jwt/claims/crit.rb +35 -0
  7. data/lib/jwt/claims/decode_verifier.rb +3 -3
  8. data/lib/jwt/claims/expiration.rb +10 -0
  9. data/lib/jwt/claims/issued_at.rb +7 -0
  10. data/lib/jwt/claims/issuer.rb +10 -0
  11. data/lib/jwt/claims/jwt_id.rb +10 -0
  12. data/lib/jwt/claims/not_before.rb +10 -0
  13. data/lib/jwt/claims/numeric.rb +22 -0
  14. data/lib/jwt/claims/required.rb +10 -0
  15. data/lib/jwt/claims/subject.rb +10 -0
  16. data/lib/jwt/claims/verification_methods.rb +20 -0
  17. data/lib/jwt/claims/verifier.rb +6 -7
  18. data/lib/jwt/claims.rb +6 -14
  19. data/lib/jwt/claims_validator.rb +4 -2
  20. data/lib/jwt/configuration/container.rb +20 -0
  21. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  23. data/lib/jwt/configuration.rb +8 -0
  24. data/lib/jwt/decode.rb +28 -68
  25. data/lib/jwt/deprecations.rb +1 -0
  26. data/lib/jwt/encode.rb +17 -56
  27. data/lib/jwt/encoded_token.rb +139 -0
  28. data/lib/jwt/error.rb +34 -0
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/compat.rb +3 -0
  31. data/lib/jwt/jwa/ecdsa.rb +3 -6
  32. data/lib/jwt/jwa/eddsa.rb +7 -6
  33. data/lib/jwt/jwa/hmac.rb +2 -3
  34. data/lib/jwt/jwa/hmac_rbnacl.rb +1 -0
  35. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +1 -0
  36. data/lib/jwt/jwa/none.rb +1 -0
  37. data/lib/jwt/jwa/ps.rb +2 -3
  38. data/lib/jwt/jwa/rsa.rb +2 -3
  39. data/lib/jwt/jwa/signing_algorithm.rb +3 -0
  40. data/lib/jwt/jwa/unsupported.rb +1 -0
  41. data/lib/jwt/jwa/wrapper.rb +1 -0
  42. data/lib/jwt/jwa.rb +11 -3
  43. data/lib/jwt/jwk/ec.rb +2 -3
  44. data/lib/jwt/jwk/hmac.rb +2 -3
  45. data/lib/jwt/jwk/key_base.rb +1 -0
  46. data/lib/jwt/jwk/key_finder.rb +1 -0
  47. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  48. data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
  49. data/lib/jwt/jwk/rsa.rb +2 -3
  50. data/lib/jwt/jwk/set.rb +2 -0
  51. data/lib/jwt/jwk.rb +1 -0
  52. data/lib/jwt/token.rb +112 -0
  53. data/lib/jwt/verify.rb +6 -0
  54. data/lib/jwt/version.rb +33 -10
  55. data/lib/jwt.rb +16 -0
  56. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce035519b0df530866af63d7dd6cb267490700e9458fc9c32fb0bb15be446f6e
4
- data.tar.gz: 94fd6a38f0e3c91407b04aa254d568e113877e7dcdcb4b923c28b6a320587119
3
+ metadata.gz: dc5cd58ec8821cfc743d4dc35f70eb1297d49cda060220d895ca334e8ab06345
4
+ data.tar.gz: 44401e7eab75b75a1cb37abc2fc58a6dcff84103bc99625880c8c3be525aabea
5
5
  SHA512:
6
- metadata.gz: aaf541769af85436646e45a61e0f3a57d75a88e640f4a811eff35d8be61285dc77b1741e79147cf435fed5b51519863fc027722d43805c3457a159d8e235f568
7
- data.tar.gz: f548c7bcfac5c31520ba0a1cc1b5dd546cf600277667109e7c33fde167138a3a29a930cf127cc51a7546e5b4c14e5e093a8279949107cd0d0062d016067a1723
6
+ metadata.gz: 80e7845a017b409105cad8d3ac3c7cc233b0cdc3e071715406b0a43e73a4b56f35fb6d5fe2c3786c10059d30809ffe0bdbb12e7438376dff9069f3a766f476f1
7
+ data.tar.gz: ab86ff7dba44c40358d5d14b18a7346412fa7081635fb2995102ddb3b26b352ff68cb9e0d001aa3e7986cea4a1ea0c096cbc1824f4be14573414cf74d15b8e6b
data/CHANGELOG.md CHANGED
@@ -1,26 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## Upcoming breaking changes
3
+ ## [v2.10.0](https://github.com/jwt/ruby-jwt/tree/v2.10.0) (2024-12-25)
4
4
 
5
- Notable changes in the upcoming **version 3.0**:
5
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.3...v2.10.0)
6
6
 
7
- - The indirect dependency to [rbnacl](https://github.com/RubyCrypto/rbnacl) will be removed:
8
- - Support for the nonstandard SHA512256 algorithm will be removed.
9
- - Support for Ed25519 will be moved to a [separate gem](https://github.com/anakinj/jwt-eddsa) for better dependency handling.
7
+ **Features:**
10
8
 
11
- - Base64 decoding will no longer fallback on the looser RFC 2045.
9
+ - JWT::Token and JWT::EncodedToken for signing and verifying tokens [#621](https://github.com/jwt/ruby-jwt/pull/621) ([@anakinj](https://github.com/anakinj))
10
+ - Detached payload support for JWT::Token and JWT::EncodedToken [#630](https://github.com/jwt/ruby-jwt/pull/630) ([@anakinj](https://github.com/anakinj))
11
+ - Skip decoding payload if b64 header is present and false [#631](https://github.com/jwt/ruby-jwt/pull/631) ([@anakinj](https://github.com/anakinj))
12
+ - Remove a few custom Rubocop configs [#638](https://github.com/jwt/ruby-jwt/pull/638) ([@anakinj](https://github.com/anakinj))
12
13
 
13
- - Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new api](https://github.com/jwt/ruby-jwt/pull/626) and lead to the following deprecations:
14
- - The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`.
15
- - The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`.
16
- - The `::JWT::JWA.create` method will be removed. No recommended alternatives.
17
- - The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`.
18
- - Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
19
- - Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
14
+ **Fixes and enhancements:**
20
15
 
21
- - The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes lead to a few deprecations and new requirements:
22
- - The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
23
- - Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
16
+ - Deprecation warnings for deprecated methods and classes [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj))
17
+ - Improved documentation for public apis [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj))
18
+ - Use correct methods when raising error during signing/verification with EdDSA [#633](https://github.com/jwt/ruby-jwt/pull/633)
19
+ - Fix JWT::EncodedToken behavior with empty string as token [#640](https://github.com/jwt/ruby-jwt/pull/640) ([@ragalie](https://github.com/ragalie))
20
+ - Deprecation warnings for rbnacl backed functionality [#641](https://github.com/jwt/ruby-jwt/pull/641) ([@anakinj](https://github.com/anakinj))
24
21
 
25
22
  ## [v2.9.3](https://github.com/jwt/ruby-jwt/tree/v2.9.3) (2024-10-03)
26
23
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # JWT
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
4
- [![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
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
5
  [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
6
6
  [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
7
7
  [![Issue Count](https://codeclimate.com/github/jwt/ruby-jwt/badges/issue_count.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
@@ -10,13 +10,30 @@ A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools
10
10
 
11
11
  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
12
 
13
- ## Announcements
14
- * Ruby 2.4 support was dropped in version 2.4.0
15
- * Ruby 1.9.3 support was dropped at December 31st, 2016.
16
- * Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
17
-
18
13
  See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
19
14
 
15
+ ## Upcoming breaking changes
16
+
17
+ Notable changes in the upcoming **version 3.0**:
18
+
19
+ - The indirect dependency to [rbnacl](https://github.com/RubyCrypto/rbnacl) will be removed:
20
+ - Support for the non-standard SHA512256 algorithm will be removed.
21
+ - Support for Ed25519 will be moved to a [separate gem](https://github.com/anakinj/jwt-eddsa) for better dependency handling.
22
+
23
+ - Base64 decoding will no longer fallback on the looser RFC 2045.
24
+
25
+ - Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new api](https://github.com/jwt/ruby-jwt/pull/626) and lead to the following deprecations:
26
+ - The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`.
27
+ - The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`.
28
+ - The `::JWT::JWA.create` method will be removed.
29
+ - The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`.
30
+ - Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
31
+ - Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
32
+
33
+ - The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes lead to a few deprecations and new requirements:
34
+ - The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
35
+ - Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
36
+
20
37
  ## Sponsors
21
38
 
22
39
  |Logo|Message|
@@ -26,20 +43,32 @@ See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
26
43
  ## Installing
27
44
 
28
45
  ### Using Rubygems:
46
+
29
47
  ```bash
30
48
  gem install jwt
31
49
  ```
32
50
 
33
51
  ### Using Bundler:
52
+
34
53
  Add the following to your Gemfile
35
54
  ```
36
55
  gem 'jwt'
37
56
  ```
57
+
38
58
  And run `bundle install`
39
59
 
60
+ Finally require the gem in your application
61
+ ```ruby
62
+ require 'jwt'
63
+ ```
64
+
40
65
  ## Algorithms and Usage
41
66
 
42
- The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cryptographic signing. Currently the jwt gem supports NONE, HMAC, RSASSA and ECDSA. If you are using 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**
67
+ 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.
68
+
69
+ Additionally the EdDSA algorithm is supported via a [separate gem](https://rubygems.org/gems/jwt-eddsa).
70
+
71
+ 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**
43
72
 
44
73
  See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
45
74
 
@@ -65,18 +94,17 @@ The stricter base64 decoding when processing tokens can be done via the `strict_
65
94
  * none - unsigned token
66
95
 
67
96
  ```ruby
68
- require 'jwt'
69
97
 
70
98
  payload = { data: 'test' }
71
99
 
72
100
  # IMPORTANT: set nil as password parameter
73
- token = JWT.encode payload, nil, 'none'
101
+ token = JWT.encode(payload, nil, 'none')
74
102
 
75
103
  # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
76
104
  puts token
77
105
 
78
106
  # Set password to nil and validation to false otherwise this won't work
79
- decoded_token = JWT.decode token, nil, false
107
+ decoded_token = JWT.decode(token, nil, false)
80
108
 
81
109
  # Array
82
110
  # [
@@ -96,12 +124,12 @@ puts decoded_token
96
124
  # 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.
97
125
  hmac_secret = 'my$ecretK3y'
98
126
 
99
- token = JWT.encode payload, hmac_secret, 'HS256'
127
+ token = JWT.encode(payload, hmac_secret, 'HS256')
100
128
 
101
129
  # eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
102
130
  puts token
103
131
 
104
- decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
132
+ decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
105
133
 
106
134
  # Array
107
135
  # [
@@ -118,15 +146,15 @@ puts decoded_token
118
146
  * RS512 - RSA using SHA-512 hash algorithm
119
147
 
120
148
  ```ruby
121
- rsa_private = OpenSSL::PKey::RSA.generate 2048
149
+ rsa_private = OpenSSL::PKey::RSA.generate(2048)
122
150
  rsa_public = rsa_private.public_key
123
151
 
124
- token = JWT.encode payload, rsa_private, 'RS256'
152
+ token = JWT.encode(payload, rsa_private, 'RS256')
125
153
 
126
154
  # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
127
155
  puts token
128
156
 
129
- decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
157
+ decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
130
158
 
131
159
  # Array
132
160
  # [
@@ -146,12 +174,12 @@ puts decoded_token
146
174
  ```ruby
147
175
  ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
148
176
 
149
- token = JWT.encode payload, ecdsa_key, 'ES256'
177
+ token = JWT.encode(payload, ecdsa_key, 'ES256')
150
178
 
151
179
  # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
152
180
  puts token
153
181
 
154
- decoded_token = JWT.decode token, ecdsa_key, true, { algorithm: 'ES256' }
182
+ decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
155
183
 
156
184
  # Array
157
185
  # [
@@ -206,12 +234,12 @@ gem 'openssl', '~> 2.1'
206
234
  rsa_private = OpenSSL::PKey::RSA.generate 2048
207
235
  rsa_public = rsa_private.public_key
208
236
 
209
- token = JWT.encode payload, rsa_private, 'PS256'
237
+ token = JWT.encode(payload, rsa_private, 'PS256')
210
238
 
211
239
  # eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
212
240
  puts token
213
241
 
214
- decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
242
+ decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
215
243
 
216
244
  # Array
217
245
  # [
@@ -221,6 +249,37 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
221
249
  puts decoded_token
222
250
  ```
223
251
 
252
+ ### Add custom header fields
253
+ Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
254
+ To add custom header fields you need to pass `header_fields` parameter
255
+
256
+ ```ruby
257
+ token = JWT.encode(payload, key, algorithm='HS256', header_fields={})
258
+ ```
259
+
260
+ **Example:**
261
+
262
+ ```ruby
263
+
264
+ payload = { data: 'test' }
265
+
266
+ # IMPORTANT: set nil as password parameter
267
+ token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
268
+
269
+ # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
270
+ puts token
271
+
272
+ # Set password to nil and validation to false otherwise this won't work
273
+ decoded_token = JWT.decode(token, nil, false)
274
+
275
+ # Array
276
+ # [
277
+ # {"data"=>"test"}, # payload
278
+ # {"typ"=>"JWT", "alg"=>"none"} # header
279
+ # ]
280
+ puts decoded_token
281
+ ```
282
+
224
283
  ### **Custom algorithms**
225
284
 
226
285
  When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods:
@@ -252,49 +311,63 @@ token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
252
311
  payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
253
312
  ```
254
313
 
255
- ## Support for reserved claim names
256
- JSON Web Token defines some reserved claim names and defines how they should be
257
- used. JWT supports these reserved claim names:
314
+ ## `JWT::Token` and `JWT::EncodedToken`
258
315
 
259
- - 'exp' (Expiration Time) Claim
260
- - 'nbf' (Not Before Time) Claim
261
- - 'iss' (Issuer) Claim
262
- - 'aud' (Audience) Claim
263
- - 'jti' (JWT ID) Claim
264
- - 'iat' (Issued At) Claim
265
- - 'sub' (Subject) Claim
316
+ The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
266
317
 
267
- ## Add custom header fields
268
- Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
269
- To add custom header fields you need to pass `header_fields` parameter
318
+ ```ruby
319
+ token = JWT::Token.new(payload: { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }, header: { kid: 'hmac' })
320
+ token.sign!(algorithm: 'HS256', key: "secret")
270
321
 
322
+ token.jwt # => "eyJhbGciOiJIUzI1N..."
323
+ ```
324
+
325
+ The `JWT::EncodedToken` can be used to create a token object that allows verification of signatures and claims
271
326
  ```ruby
272
- token = JWT.encode payload, key, algorithm='HS256', header_fields={}
327
+ encoded_token = JWT::EncodedToken.new(token.jwt)
328
+
329
+ encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
330
+ encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
331
+ encoded_token.verify_claims!(:exp, :jti)
332
+ encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
333
+ encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
334
+ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
335
+ encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
273
336
  ```
274
337
 
275
- **Example:**
338
+ ### Detached payload
339
+
340
+ The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
276
341
 
277
342
  ```ruby
278
- require 'jwt'
343
+ token = JWT::Token.new(payload: { pay: 'load' })
344
+ token.sign!(algorithm: 'HS256', key: "secret")
345
+ token.detach_payload!
346
+ token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
347
+ token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
348
+ ```
279
349
 
280
- payload = { data: 'test' }
350
+ The `JWT::EncodedToken` class can be used to decode a token with a detached payload by providing the payload to the token instance in separate.
281
351
 
282
- # IMPORTANT: set nil as password parameter
283
- token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
352
+ ```ruby
353
+ encoded_token = JWT::EncodedToken.new(token.jwt)
354
+ encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
355
+ encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
356
+ encoded_token.payload # => {"pay"=>"load"}
357
+ ```
284
358
 
285
- # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
286
- puts token
359
+ ## Claims
287
360
 
288
- # Set password to nil and validation to false otherwise this won't work
289
- decoded_token = JWT.decode token, nil, false
361
+ JSON Web Token defines some reserved claim names and defines how they should be
362
+ used. JWT supports these reserved claim names:
290
363
 
291
- # Array
292
- # [
293
- # {"data"=>"test"}, # payload
294
- # {"typ"=>"JWT", "alg"=>"none"} # header
295
- # ]
296
- puts decoded_token
297
- ```
364
+ - 'exp' (Expiration Time) Claim
365
+ - 'nbf' (Not Before Time) Claim
366
+ - 'iss' (Issuer) Claim
367
+ - 'aud' (Audience) Claim
368
+ - 'jti' (JWT ID) Claim
369
+ - 'iat' (Issued At) Claim
370
+ - 'sub' (Subject) Claim
298
371
 
299
372
  ### Expiration Time Claim
300
373
 
@@ -308,10 +381,10 @@ From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.i
308
381
  exp = Time.now.to_i + 4 * 3600
309
382
  exp_payload = { data: 'data', exp: exp }
310
383
 
311
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
384
+ token = JWT.encode(exp_payload, hmac_secret, 'HS256')
312
385
 
313
386
  begin
314
- decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
387
+ decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
315
388
  rescue JWT::ExpiredSignature
316
389
  # Handle expired token, e.g. logout user or deny access
317
390
  end
@@ -320,7 +393,7 @@ end
320
393
  The Expiration Claim verification can be disabled.
321
394
  ```ruby
322
395
  # Decode token without raising JWT::ExpiredSignature error
323
- JWT.decode token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }
396
+ JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
324
397
  ```
325
398
 
326
399
  **Adding Leeway**
@@ -332,11 +405,11 @@ leeway = 30 # seconds
332
405
  exp_payload = { data: 'data', exp: exp }
333
406
 
334
407
  # build expired token
335
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
408
+ token = JWT.encode(exp_payload, hmac_secret, 'HS256')
336
409
 
337
410
  begin
338
411
  # add leeway to ensure the token is still accepted
339
- decoded_token = JWT.decode token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }
412
+ decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
340
413
  rescue JWT::ExpiredSignature
341
414
  # Handle expired token, e.g. logout user or deny access
342
415
  end
@@ -354,10 +427,10 @@ From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.o
354
427
  nbf = Time.now.to_i - 3600
355
428
  nbf_payload = { data: 'data', nbf: nbf }
356
429
 
357
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
430
+ token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
358
431
 
359
432
  begin
360
- decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
433
+ decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
361
434
  rescue JWT::ImmatureSignature
362
435
  # Handle invalid token, e.g. logout user or deny access
363
436
  end
@@ -366,7 +439,7 @@ end
366
439
  The Not Before Claim verification can be disabled.
367
440
  ```ruby
368
441
  # Decode token without raising JWT::ImmatureSignature error
369
- JWT.decode token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }
442
+ JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
370
443
  ```
371
444
 
372
445
  **Adding Leeway**
@@ -378,11 +451,11 @@ leeway = 30
378
451
  nbf_payload = { data: 'data', nbf: nbf }
379
452
 
380
453
  # build expired token
381
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
454
+ token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
382
455
 
383
456
  begin
384
457
  # add leeway to ensure the token is valid
385
- decoded_token = JWT.decode token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }
458
+ decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
386
459
  rescue JWT::ImmatureSignature
387
460
  # Handle invalid token, e.g. logout user or deny access
388
461
  end
@@ -400,11 +473,11 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
400
473
  iss = 'My Awesome Company Inc. or https://my.awesome.website/'
401
474
  iss_payload = { data: 'data', iss: iss }
402
475
 
403
- token = JWT.encode iss_payload, hmac_secret, 'HS256'
476
+ token = JWT.encode(iss_payload, hmac_secret, 'HS256')
404
477
 
405
478
  begin
406
479
  # Add iss to the validation to check if the token has been manipulated
407
- decoded_token = JWT.decode token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }
480
+ decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
408
481
  rescue JWT::InvalidIssuerError
409
482
  # Handle invalid token, e.g. logout user or deny access
410
483
  end
@@ -415,24 +488,24 @@ On supported ruby versions (>= 2.5) you can also delegate to methods, on older v
415
488
  to convert them to proc (using `to_proc`)
416
489
 
417
490
  ```ruby
418
- JWT.decode token, hmac_secret, true,
491
+ JWT.decode(token, hmac_secret, true,
419
492
  iss: %r'https://my.awesome.website/',
420
493
  verify_iss: true,
421
- algorithm: 'HS256'
494
+ algorithm: 'HS256')
422
495
  ```
423
496
 
424
497
  ```ruby
425
- JWT.decode token, hmac_secret, true,
498
+ JWT.decode(token, hmac_secret, true,
426
499
  iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
427
500
  verify_iss: true,
428
- algorithm: 'HS256'
501
+ algorithm: 'HS256')
429
502
  ```
430
503
 
431
504
  ```ruby
432
- JWT.decode token, hmac_secret, true,
505
+ JWT.decode(token, hmac_secret, true,
433
506
  iss: method(:valid_issuer?),
434
507
  verify_iss: true,
435
- algorithm: 'HS256'
508
+ algorithm: 'HS256')
436
509
 
437
510
  # somewhere in the same class:
438
511
  def valid_issuer?(issuer)
@@ -450,11 +523,11 @@ From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org
450
523
  aud = ['Young', 'Old']
451
524
  aud_payload = { data: 'data', aud: aud }
452
525
 
453
- token = JWT.encode aud_payload, hmac_secret, 'HS256'
526
+ token = JWT.encode(aud_payload, hmac_secret, 'HS256')
454
527
 
455
528
  begin
456
529
  # Add aud to the validation to check if the token has been manipulated
457
- decoded_token = JWT.decode token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }
530
+ decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
458
531
  rescue JWT::InvalidAudError
459
532
  # Handle invalid token, e.g. logout user or deny access
460
533
  puts 'Audience Error'
@@ -473,15 +546,15 @@ jti_raw = [hmac_secret, iat].join(':').to_s
473
546
  jti = Digest::MD5.hexdigest(jti_raw)
474
547
  jti_payload = { data: 'data', iat: iat, jti: jti }
475
548
 
476
- token = JWT.encode jti_payload, hmac_secret, 'HS256'
549
+ token = JWT.encode(jti_payload, hmac_secret, 'HS256')
477
550
 
478
551
  begin
479
552
  # If :verify_jti is true, validation will pass if a JTI is present
480
- #decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }
553
+ #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
481
554
  # Alternatively, pass a proc with your own code to check if the JTI has already been used
482
- decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }
555
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
483
556
  # or
484
- decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }
557
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
485
558
  rescue JWT::InvalidJtiError
486
559
  # Handle invalid token, e.g. logout user or deny access
487
560
  puts 'Error'
@@ -500,11 +573,11 @@ From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.or
500
573
  iat = Time.now.to_i
501
574
  iat_payload = { data: 'data', iat: iat }
502
575
 
503
- token = JWT.encode iat_payload, hmac_secret, 'HS256'
576
+ token = JWT.encode(iat_payload, hmac_secret, 'HS256')
504
577
 
505
578
  begin
506
579
  # Add iat to the validation to check if the token has been manipulated
507
- decoded_token = JWT.decode token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }
580
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
508
581
  rescue JWT::InvalidIatError
509
582
  # Handle invalid token, e.g. logout user or deny access
510
583
  end
@@ -520,11 +593,11 @@ From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/
520
593
  sub = 'Subject'
521
594
  sub_payload = { data: 'data', sub: sub }
522
595
 
523
- token = JWT.encode sub_payload, hmac_secret, 'HS256'
596
+ token = JWT.encode(sub_payload, hmac_secret, 'HS256')
524
597
 
525
598
  begin
526
599
  # Add sub to the validation to check if the token has been manipulated
527
- decoded_token = JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }
600
+ decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
528
601
  rescue JWT::InvalidSubError
529
602
  # Handle invalid token, e.g. logout user or deny access
530
603
  end
@@ -546,8 +619,6 @@ JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
546
619
  JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
547
620
  ```
548
621
 
549
-
550
-
551
622
  ### Finding a Key
552
623
 
553
624
  To dynamically find the key for verifying the JWT signature, pass a block to the decode block. The block receives headers and the original payload as parameters. It should return with the key to verify the signature that was used to sign the JWT.
@@ -558,7 +629,7 @@ iss_payload = { data: 'data', iss: issuers.first }
558
629
 
559
630
  secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
560
631
 
561
- token = JWT.encode iss_payload, hmac_secret, 'HS256'
632
+ token = JWT.encode(iss_payload, hmac_secret, 'HS256')
562
633
 
563
634
  begin
564
635
  # Add iss to the validation to check if the token has been manipulated
@@ -575,7 +646,7 @@ end
575
646
  You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
576
647
  ```ruby
577
648
  # Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
578
- JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
649
+ JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
579
650
  ```
580
651
 
581
652
  ### X.509 certificates in x5c header
@@ -599,7 +670,7 @@ rescue JWT::DecodeError
599
670
  end
600
671
  ```
601
672
 
602
- ### JSON Web Key (JWK)
673
+ ## JSON Web Key (JWK)
603
674
 
604
675
  JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve.
605
676
 
@@ -626,7 +697,6 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
626
697
  JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
627
698
  ```
628
699
 
629
-
630
700
  The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
631
701
  This can be used to implement caching of remotely fetched JWK Sets.
632
702
 
data/lib/jwt/base64.rb CHANGED
@@ -4,15 +4,18 @@ require 'base64'
4
4
 
5
5
  module JWT
6
6
  # Base64 encoding and decoding
7
+ # @api private
7
8
  class Base64
8
9
  class << self
9
10
  # Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).
11
+ # @api private
10
12
  def url_encode(str)
11
13
  ::Base64.urlsafe_encode64(str, padding: false)
12
14
  end
13
15
 
14
16
  # Decode a string with URL-safe Base64 complying with RFC 4648.
15
17
  # Deprecated support for RFC 2045 remains for now. ("All line breaks or other characters not found in Table 1 must be ignored by decoding software")
18
+ # @api private
16
19
  def url_decode(str)
17
20
  ::Base64.urlsafe_decode64(str)
18
21
  rescue ArgumentError => e
@@ -2,11 +2,21 @@
2
2
 
3
3
  module JWT
4
4
  module Claims
5
+ # The Audience class is responsible for validating the audience claim ('aud') in a JWT token.
5
6
  class Audience
7
+ # Initializes a new Audience instance.
8
+ #
9
+ # @param expected_audience [String, Array<String>] the expected audience(s) for the JWT token.
6
10
  def initialize(expected_audience:)
7
11
  @expected_audience = expected_audience
8
12
  end
9
13
 
14
+ # Verifies the audience claim ('aud') in the JWT token.
15
+ #
16
+ # @param context [Object] the context containing the JWT payload.
17
+ # @param _args [Hash] additional arguments (not used).
18
+ # @raise [JWT::InvalidAudError] if the audience claim is invalid.
19
+ # @return [nil]
10
20
  def verify!(context:, **_args)
11
21
  aud = context.payload['aud']
12
22
  raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || '<none>'}" if ([*aud] & [*expected_audience]).empty?
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ # Responsible of validation the crit header
6
+ class Crit
7
+ # Initializes a new Crit instance.
8
+ #
9
+ # @param expected_crits [String] the expected crit header values for the JWT token.
10
+ def initialize(expected_crits:)
11
+ @expected_crits = Array(expected_crits)
12
+ end
13
+
14
+ # Verifies the critical claim ('crit') in the JWT token header.
15
+ #
16
+ # @param context [Object] the context containing the JWT payload and header.
17
+ # @param _args [Hash] additional arguments (not used).
18
+ # @raise [JWT::InvalidCritError] if the crit claim is invalid.
19
+ # @return [nil]
20
+ def verify!(context:, **_args)
21
+ raise(JWT::InvalidCritError, 'Crit header missing') unless context.header['crit']
22
+ raise(JWT::InvalidCritError, 'Crit header should be an array') unless context.header['crit'].is_a?(Array)
23
+
24
+ missing = (expected_crits - context.header['crit'])
25
+ raise(JWT::InvalidCritError, "Crit header missing expected values: #{missing.join(', ')}") if missing.any?
26
+
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :expected_crits
33
+ end
34
+ end
35
+ end