jwt 2.10.1 → 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,62 +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
- 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.
12
+ See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes and [upgrade guide](UPGRADING.md) for upgrading between major versions.
36
13
 
37
14
  ## Sponsors
38
15
 
39
- |Logo|Message|
40
- |-|-|
41
- |![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) |
42
19
 
43
20
  ## Installing
44
21
 
45
- ### Using Rubygems:
22
+ ### Using Rubygems
46
23
 
47
24
  ```bash
48
25
  gem install jwt
49
26
  ```
50
27
 
51
- ### Using Bundler:
28
+ ### Using Bundler
52
29
 
53
30
  Add the following to your Gemfile
54
- ```
31
+
32
+ ```bash
55
33
  gem 'jwt'
56
34
  ```
57
35
 
58
36
  And run `bundle install`
59
37
 
60
38
  Finally require the gem in your application
39
+
61
40
  ```ruby
62
41
  require 'jwt'
63
42
  ```
@@ -66,218 +45,113 @@ require 'jwt'
66
45
 
67
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.
68
47
 
69
- 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).
70
49
 
71
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**
72
51
 
73
- See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
74
-
75
- ### Deprecation warnings
76
-
77
- 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.
78
-
79
- ```ruby
80
- JWT.configuration.deprecation_warnings = :warn # default is :once
81
- ```
82
-
83
- ### Base64 decoding
84
-
85
- 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).
86
-
87
- The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
88
- ```ruby
89
- JWT.configuration.strict_base64_decoding = true # default is false
90
- ```
52
+ See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
91
53
 
92
54
  ### **NONE**
93
55
 
94
- * none - unsigned token
56
+ - none - unsigned token
95
57
 
96
58
  ```ruby
97
-
98
59
  payload = { data: 'test' }
60
+ token = JWT.encode(payload, nil, 'none')
61
+ # => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
99
62
 
100
- # IMPORTANT: set nil as password parameter
101
- token = JWT.encode(payload, nil, 'none')
102
-
103
- # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
104
- puts token
105
-
106
- # Set password to nil and validation to false otherwise this won't work
107
- decoded_token = JWT.decode(token, nil, false)
108
-
109
- # Array
110
- # [
111
- # {"data"=>"test"}, # payload
112
- # {"alg"=>"none"} # header
113
- # ]
114
- puts decoded_token
63
+ decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
64
+ # => [
65
+ # {"data"=>"test"}, # payload
66
+ # {"alg"=>"none"} # header
67
+ # ]
115
68
  ```
116
69
 
117
70
  ### **HMAC**
118
71
 
119
- * HS256 - HMAC using SHA-256 hash algorithm
120
- * HS384 - HMAC using SHA-384 hash algorithm
121
- * 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
122
75
 
123
76
  ```ruby
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.
77
+ payload = { data: 'test' }
125
78
  hmac_secret = 'my$ecretK3y'
126
79
 
127
80
  token = JWT.encode(payload, hmac_secret, 'HS256')
128
-
129
- # eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
130
- puts token
81
+ # => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
131
82
 
132
83
  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
133
-
134
- # Array
135
- # [
136
- # {"data"=>"test"}, # payload
137
- # {"alg"=>"HS256"} # header
138
- # ]
139
- puts decoded_token
84
+ # => [
85
+ # {"data"=>"test"}, # payload
86
+ # {"alg"=>"HS256"} # header
87
+ # ]
140
88
  ```
141
89
 
142
90
  ### **RSA**
143
91
 
144
- * RS256 - RSA using SHA-256 hash algorithm
145
- * RS384 - RSA using SHA-384 hash algorithm
146
- * 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
147
95
 
148
96
  ```ruby
97
+ payload = { data: 'test' }
149
98
  rsa_private = OpenSSL::PKey::RSA.generate(2048)
150
- rsa_public = rsa_private.public_key
99
+ rsa_public = rsa_private.public_key
151
100
 
152
101
  token = JWT.encode(payload, rsa_private, 'RS256')
153
-
154
- # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
155
- puts token
102
+ # => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
156
103
 
157
104
  decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
158
-
159
- # Array
160
- # [
161
- # {"data"=>"test"}, # payload
162
- # {"alg"=>"RS256"} # header
163
- # ]
164
- puts decoded_token
105
+ # => [
106
+ # {"data"=>"test"}, # payload
107
+ # {"alg"=>"RS256"} # header
108
+ # ]
165
109
  ```
166
110
 
167
111
  ### **ECDSA**
168
112
 
169
- * ES256 - ECDSA using P-256 and SHA-256
170
- * ES384 - ECDSA using P-384 and SHA-384
171
- * ES512 - ECDSA using P-521 and SHA-512
172
- * 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
173
117
 
174
118
  ```ruby
119
+ payload = { data: 'test' }
175
120
  ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
176
121
 
177
122
  token = JWT.encode(payload, ecdsa_key, 'ES256')
178
-
179
- # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
180
- puts token
123
+ # => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
181
124
 
182
125
  decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
183
-
184
- # Array
185
- # [
186
- # {"test"=>"data"}, # payload
187
- # {"alg"=>"ES256"} # header
188
- # ]
189
- puts decoded_token
126
+ # => [
127
+ # {"test"=>"data"}, # payload
128
+ # {"alg"=>"ES256"} # header
129
+ # ]
190
130
  ```
191
131
 
192
- ### **EDDSA**
132
+ ### **EdDSA**
193
133
 
194
- In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
195
-
196
- ```ruby
197
- gem 'rbnacl'
198
- ```
199
-
200
- For more detailed installation instruction check the official [repository](https://github.com/RubyCrypto/rbnacl) on GitHub.
201
-
202
- * ED25519
203
-
204
- ```ruby
205
- private_key = RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF')
206
- public_key = private_key.verify_key
207
- token = JWT.encode payload, private_key, 'ED25519'
208
-
209
- # eyJhbGciOiJFRDI1NTE5In0.eyJkYXRhIjoidGVzdCJ9.6xIztXyOupskddGA_RvKU76V9b2dCQUYhoZEVFnRimJoPYIzZ2Fm47CWw8k2NTCNpgfAuxg9OXjaiVK7MvrbCQ
210
- puts token
211
-
212
- decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
213
- # Array
214
- # [
215
- # {"test"=>"data"}, # payload
216
- # {"alg"=>"ED25519"} # header
217
- # ]
218
-
219
- ```
134
+ Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
220
135
 
221
136
  ### **RSASSA-PSS**
222
137
 
223
- In order to use this algorithm you need to add the `openssl` gem to your `Gemfile` with a version greater or equal to `2.1`.
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
224
141
 
225
142
  ```ruby
226
- gem 'openssl', '~> 2.1'
227
- ```
228
-
229
- * PS256 - RSASSA-PSS using SHA-256 hash algorithm
230
- * PS384 - RSASSA-PSS using SHA-384 hash algorithm
231
- * PS512 - RSASSA-PSS using SHA-512 hash algorithm
232
-
233
- ```ruby
234
- rsa_private = OpenSSL::PKey::RSA.generate 2048
235
- rsa_public = rsa_private.public_key
143
+ payload = { data: 'test' }
144
+ rsa_private = OpenSSL::PKey::RSA.generate(2048)
145
+ rsa_public = rsa_private.public_key
236
146
 
237
147
  token = JWT.encode(payload, rsa_private, 'PS256')
238
-
239
- # eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
240
- puts token
148
+ # => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
241
149
 
242
150
  decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
243
-
244
- # Array
245
- # [
246
- # {"data"=>"test"}, # payload
247
- # {"alg"=>"PS256"} # header
248
- # ]
249
- puts decoded_token
250
- ```
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
151
+ # => [
152
+ # {"data"=>"test"}, # payload
153
+ # {"alg"=>"PS256"} # header
154
+ # ]
281
155
  ```
282
156
 
283
157
  ### **Custom algorithms**
@@ -289,7 +163,6 @@ When encoding or decoding a token, you can pass in a custom object through the `
289
163
 
290
164
  For customization options check the details from `JWT::JWA::SigningAlgorithm`.
291
165
 
292
-
293
166
  ```ruby
294
167
  module CustomHS512Algorithm
295
168
  extend JWT::JWA::SigningAlgorithm
@@ -307,22 +180,56 @@ module CustomHS512Algorithm
307
180
  end
308
181
  end
309
182
 
310
- token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
311
- 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
+ # ]
312
210
  ```
313
211
 
314
212
  ## `JWT::Token` and `JWT::EncodedToken`
315
213
 
316
214
  The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
317
215
 
216
+ ### Signing and encoding a token
217
+
318
218
  ```ruby
319
- 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)
320
223
  token.sign!(algorithm: 'HS256', key: "secret")
321
224
 
322
- token.jwt # => "eyJhbGciOiJIUzI1N..."
225
+ token.jwt
226
+ # => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
323
227
  ```
324
228
 
325
- 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
+
326
233
  ```ruby
327
234
  encoded_token = JWT::EncodedToken.new(token.jwt)
328
235
 
@@ -335,6 +242,68 @@ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
335
242
  encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
336
243
  ```
337
244
 
245
+ The `JWT::EncodedToken#verify!` method can be used to verify signature and claim verification in one go. The `exp` claim is verified by default.
246
+
247
+ ```ruby
248
+ encoded_token = JWT::EncodedToken.new(token.jwt)
249
+ encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
250
+ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
251
+ encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
252
+ ```
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
+
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.
277
+
278
+ An example on using the built-in JWK keyfinder.
279
+
280
+ ```ruby
281
+ # Create and sign a token
282
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
283
+ token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
284
+ token.sign!(algorithm: 'RS256', key: jwk.signing_key)
285
+
286
+ # Create keyfinder object, verify and decode token
287
+ key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
288
+ encoded_token = JWT::EncodedToken.new(token.jwt)
289
+ encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
290
+ encoded_token.payload # => { 'pay' => 'load' }
291
+ ```
292
+
293
+ Using a custom keyfinder proc.
294
+
295
+ ```ruby
296
+ # Create and sign a token
297
+ key = OpenSSL::PKey::RSA.generate(2048)
298
+ token = JWT::Token.new(payload: { pay: 'load' })
299
+ token.sign!(algorithm: 'RS256', key: key)
300
+
301
+ # Verify and decode token
302
+ encoded_token = JWT::EncodedToken.new(token.jwt)
303
+ encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
304
+ encoded_token.payload # => { 'pay' => 'load' }
305
+ ```
306
+
338
307
  ### Detached payload
339
308
 
340
309
  The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
@@ -361,21 +330,19 @@ encoded_token.payload # => {"pay"=>"load"}
361
330
  JSON Web Token defines some reserved claim names and defines how they should be
362
331
  used. JWT supports these reserved claim names:
363
332
 
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
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
371
340
 
372
341
  ### Expiration Time Claim
373
342
 
374
343
  From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
375
344
 
376
- > 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.
377
-
378
- **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.
379
346
 
380
347
  ```ruby
381
348
  exp = Time.now.to_i + 4 * 3600
@@ -391,12 +358,13 @@ end
391
358
  ```
392
359
 
393
360
  The Expiration Claim verification can be disabled.
361
+
394
362
  ```ruby
395
363
  # Decode token without raising JWT::ExpiredSignature error
396
364
  JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
397
365
  ```
398
366
 
399
- **Adding Leeway**
367
+ Leeway and the exp claim.
400
368
 
401
369
  ```ruby
402
370
  exp = Time.now.to_i - 10
@@ -419,9 +387,7 @@ end
419
387
 
420
388
  From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5):
421
389
 
422
- > 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.
423
-
424
- **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.
425
391
 
426
392
  ```ruby
427
393
  nbf = Time.now.to_i - 3600
@@ -437,12 +403,13 @@ end
437
403
  ```
438
404
 
439
405
  The Not Before Claim verification can be disabled.
406
+
440
407
  ```ruby
441
408
  # Decode token without raising JWT::ImmatureSignature error
442
409
  JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
443
410
  ```
444
411
 
445
- **Adding Leeway**
412
+ Leeway and the nbf claim.
446
413
 
447
414
  ```ruby
448
415
  nbf = Time.now.to_i + 10
@@ -465,7 +432,7 @@ end
465
432
 
466
433
  From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1):
467
434
 
468
- > 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.
469
436
 
470
437
  You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
471
438
 
@@ -517,7 +484,7 @@ end
517
484
 
518
485
  From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
519
486
 
520
- > 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.
521
488
 
522
489
  ```ruby
523
490
  aud = ['Young', 'Old']
@@ -565,9 +532,7 @@ end
565
532
 
566
533
  From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
567
534
 
568
- > 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.
569
-
570
- **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.
571
536
 
572
537
  ```ruby
573
538
  iat = Time.now.to_i
@@ -587,7 +552,7 @@ end
587
552
 
588
553
  From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
589
554
 
590
- > 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.
591
556
 
592
557
  ```ruby
593
558
  sub = 'Subject'
@@ -608,6 +573,7 @@ end
608
573
  The JWT claim verifications can be used to verify any Hash to include expected keys and values.
609
574
 
610
575
  A few example on verifying the claims for a payload:
576
+
611
577
  ```ruby
612
578
  JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
613
579
  JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
@@ -644,6 +610,7 @@ end
644
610
  ### Required Claims
645
611
 
646
612
  You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
613
+
647
614
  ```ruby
648
615
  # Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
649
616
  JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
@@ -697,13 +664,14 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
697
664
  JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
698
665
  ```
699
666
 
700
- 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.
701
668
  This can be used to implement caching of remotely fetched JWK Sets.
702
669
 
703
- 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`.
704
672
  The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
705
673
 
706
- Tokens without a specified `kid` are rejected by default.
674
+ Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default.
707
675
  This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
708
676
 
709
677
  ```ruby
@@ -782,7 +750,7 @@ jwk_hash = jwk.export
782
750
  thumbprint_as_the_kid = jwk_hash[:kid]
783
751
  ```
784
752
 
785
- # Development and testing
753
+ ## Development and testing
786
754
 
787
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.
788
756
 
@@ -791,7 +759,7 @@ bundle install
791
759
  bundle exec appraisal rake test
792
760
  ```
793
761
 
794
- # Releasing
762
+ ## Releasing
795
763
 
796
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:
797
765
 
@@ -802,6 +770,7 @@ rake release:source_control_push
802
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.
803
771
 
804
772
  ## How to contribute
773
+
805
774
  See [CONTRIBUTING](CONTRIBUTING.md).
806
775
 
807
776
  ## Contributors