jwt 2.10.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -32
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +190 -221
- data/UPGRADING.md +47 -0
- data/lib/jwt/base64.rb +1 -10
- data/lib/jwt/claims/numeric.rb +0 -32
- data/lib/jwt/claims.rb +0 -7
- data/lib/jwt/configuration/container.rb +0 -1
- data/lib/jwt/decode.rb +18 -15
- data/lib/jwt/encoded_token.rb +111 -14
- data/lib/jwt/error.rb +0 -3
- data/lib/jwt/jwa/ecdsa.rb +25 -4
- data/lib/jwt/jwa/hmac.rb +0 -4
- data/lib/jwt/jwa/ps.rb +1 -0
- data/lib/jwt/jwa/rsa.rb +1 -0
- data/lib/jwt/jwa/signing_algorithm.rb +0 -1
- data/lib/jwt/jwa.rb +71 -26
- data/lib/jwt/jwk/ec.rb +52 -62
- data/lib/jwt/jwk/hmac.rb +3 -3
- data/lib/jwt/jwk/key_base.rb +15 -1
- data/lib/jwt/jwk/key_finder.rb +35 -9
- data/lib/jwt/jwk/rsa.rb +5 -1
- data/lib/jwt/jwk.rb +0 -1
- data/lib/jwt/token.rb +26 -7
- data/lib/jwt/version.rb +3 -19
- data/lib/jwt.rb +1 -7
- data/ruby-jwt.gemspec +2 -0
- metadata +33 -14
- data/lib/jwt/claims/verification_methods.rb +0 -20
- data/lib/jwt/claims_validator.rb +0 -18
- data/lib/jwt/deprecations.rb +0 -49
- data/lib/jwt/jwa/compat.rb +0 -32
- data/lib/jwt/jwa/eddsa.rb +0 -35
- data/lib/jwt/jwa/hmac_rbnacl.rb +0 -50
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -47
- data/lib/jwt/jwa/wrapper.rb +0 -44
- data/lib/jwt/jwk/okp_rbnacl.rb +0 -109
- data/lib/jwt/verify.rb +0 -40
data/README.md
CHANGED
@@ -2,62 +2,41 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/jwt)
|
4
4
|
[](https://github.com/jwt/ruby-jwt/actions)
|
5
|
-
[](https://codeclimate.com/github/jwt/ruby-jwt)
|
5
|
+
[](https://qlty.sh/gh/jwt/projects/ruby-jwt)
|
6
|
+
[](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
|
-
|
16
|
+
| Logo | Message |
|
17
|
+
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
18
|
+
|  | 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 [
|
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
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
#
|
104
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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
|
-
#
|
135
|
-
#
|
136
|
-
#
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
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
|
-
#
|
160
|
-
#
|
161
|
-
#
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
# {"alg"=>"ES256"} # header
|
188
|
-
# ]
|
189
|
-
puts decoded_token
|
126
|
+
# => [
|
127
|
+
# {"test"=>"data"}, # payload
|
128
|
+
# {"alg"=>"ES256"} # header
|
129
|
+
# ]
|
190
130
|
```
|
191
131
|
|
192
|
-
### **
|
132
|
+
### **EdDSA**
|
193
133
|
|
194
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
245
|
-
#
|
246
|
-
#
|
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
|
-
|
311
|
-
|
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
|
-
|
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
|
225
|
+
token.jwt
|
226
|
+
# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
|
323
227
|
```
|
324
228
|
|
325
|
-
|
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: { algorithm: ["HS256", "HS512"], 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
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|