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