jwt 2.7.1 → 2.10.3
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 +157 -27
- data/README.md +207 -101
- data/lib/jwt/base64.rb +19 -2
- data/lib/jwt/claims/audience.rb +30 -0
- data/lib/jwt/claims/crit.rb +35 -0
- data/lib/jwt/claims/decode_verifier.rb +40 -0
- data/lib/jwt/claims/expiration.rb +32 -0
- data/lib/jwt/claims/issued_at.rb +22 -0
- data/lib/jwt/claims/issuer.rb +34 -0
- data/lib/jwt/claims/jwt_id.rb +35 -0
- data/lib/jwt/claims/not_before.rb +32 -0
- data/lib/jwt/claims/numeric.rb +77 -0
- data/lib/jwt/claims/required.rb +33 -0
- data/lib/jwt/claims/subject.rb +30 -0
- data/lib/jwt/claims/verification_methods.rb +20 -0
- data/lib/jwt/claims/verifier.rb +61 -0
- data/lib/jwt/claims.rb +74 -0
- data/lib/jwt/claims_validator.rb +6 -25
- data/lib/jwt/configuration/container.rb +34 -3
- data/lib/jwt/configuration/decode_configuration.rb +24 -0
- data/lib/jwt/configuration/jwk_configuration.rb +2 -1
- data/lib/jwt/configuration.rb +8 -0
- data/lib/jwt/decode.rb +34 -82
- data/lib/jwt/deprecations.rb +49 -0
- data/lib/jwt/encode.rb +18 -67
- data/lib/jwt/encoded_token.rb +139 -0
- data/lib/jwt/error.rb +35 -0
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/compat.rb +32 -0
- data/lib/jwt/{algos → jwa}/ecdsa.rb +40 -26
- data/lib/jwt/jwa/eddsa.rb +35 -0
- data/lib/jwt/{algos → jwa}/hmac.rb +31 -20
- data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
- data/lib/jwt/jwa/none.rb +24 -0
- data/lib/jwt/jwa/ps.rb +35 -0
- data/lib/jwt/jwa/rsa.rb +35 -0
- data/lib/jwt/jwa/signing_algorithm.rb +63 -0
- data/lib/jwt/jwa/unsupported.rb +20 -0
- data/lib/jwt/jwa/wrapper.rb +44 -0
- data/lib/jwt/jwa.rb +58 -0
- data/lib/jwt/jwk/ec.rb +44 -30
- data/lib/jwt/jwk/hmac.rb +2 -3
- data/lib/jwt/jwk/key_base.rb +4 -1
- data/lib/jwt/jwk/key_finder.rb +5 -4
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
- data/lib/jwt/jwk/rsa.rb +2 -3
- data/lib/jwt/jwk/set.rb +3 -1
- data/lib/jwt/jwk.rb +2 -1
- data/lib/jwt/token.rb +112 -0
- data/lib/jwt/verify.rb +16 -89
- data/lib/jwt/version.rb +33 -11
- data/lib/jwt/x5c_key_finder.rb +2 -5
- data/lib/jwt.rb +24 -1
- data/ruby-jwt.gemspec +4 -0
- metadata +76 -18
- data/lib/jwt/algos/algo_wrapper.rb +0 -26
- data/lib/jwt/algos/eddsa.rb +0 -33
- data/lib/jwt/algos/hmac_rbnacl.rb +0 -53
- data/lib/jwt/algos/hmac_rbnacl_fixed.rb +0 -52
- data/lib/jwt/algos/none.rb +0 -19
- data/lib/jwt/algos/ps.rb +0 -41
- data/lib/jwt/algos/rsa.rb +0 -23
- data/lib/jwt/algos/unsupported.rb +0 -19
- data/lib/jwt/algos.rb +0 -66
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# JWT
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/jwt)
|
|
4
|
-
[](https://github.com/jwt/ruby-jwt/actions)
|
|
4
|
+
[](https://github.com/jwt/ruby-jwt/actions)
|
|
5
5
|
[](https://codeclimate.com/github/jwt/ruby-jwt)
|
|
6
6
|
[](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
|
|
7
7
|
[](https://codeclimate.com/github/jwt/ruby-jwt)
|
|
@@ -10,13 +10,30 @@ A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools
|
|
|
10
10
|
|
|
11
11
|
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
|
12
12
|
|
|
13
|
-
## Announcements
|
|
14
|
-
* Ruby 2.4 support was dropped in version 2.4.0
|
|
15
|
-
* Ruby 1.9.3 support was dropped at December 31st, 2016.
|
|
16
|
-
* Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
|
|
17
|
-
|
|
18
13
|
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
|
19
14
|
|
|
15
|
+
## Upcoming breaking changes
|
|
16
|
+
|
|
17
|
+
Notable changes in the upcoming **version 3.0**:
|
|
18
|
+
|
|
19
|
+
- The indirect dependency to [rbnacl](https://github.com/RubyCrypto/rbnacl) will be removed:
|
|
20
|
+
- Support for the non-standard SHA512256 algorithm will be removed.
|
|
21
|
+
- Support for Ed25519 will be moved to a [separate gem](https://github.com/anakinj/jwt-eddsa) for better dependency handling.
|
|
22
|
+
|
|
23
|
+
- Base64 decoding will no longer fallback on the looser RFC 2045.
|
|
24
|
+
|
|
25
|
+
- Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new api](https://github.com/jwt/ruby-jwt/pull/626) and lead to the following deprecations:
|
|
26
|
+
- The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`.
|
|
27
|
+
- The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`.
|
|
28
|
+
- The `::JWT::JWA.create` method will be removed.
|
|
29
|
+
- The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`.
|
|
30
|
+
- Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
|
|
31
|
+
- Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
|
|
32
|
+
|
|
33
|
+
- The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes lead to a few deprecations and new requirements:
|
|
34
|
+
- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
|
|
35
|
+
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
|
|
36
|
+
|
|
20
37
|
## Sponsors
|
|
21
38
|
|
|
22
39
|
|Logo|Message|
|
|
@@ -26,40 +43,68 @@ See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
|
|
26
43
|
## Installing
|
|
27
44
|
|
|
28
45
|
### Using Rubygems:
|
|
46
|
+
|
|
29
47
|
```bash
|
|
30
48
|
gem install jwt
|
|
31
49
|
```
|
|
32
50
|
|
|
33
51
|
### Using Bundler:
|
|
52
|
+
|
|
34
53
|
Add the following to your Gemfile
|
|
35
54
|
```
|
|
36
55
|
gem 'jwt'
|
|
37
56
|
```
|
|
57
|
+
|
|
38
58
|
And run `bundle install`
|
|
39
59
|
|
|
60
|
+
Finally require the gem in your application
|
|
61
|
+
```ruby
|
|
62
|
+
require 'jwt'
|
|
63
|
+
```
|
|
64
|
+
|
|
40
65
|
## Algorithms and Usage
|
|
41
66
|
|
|
42
|
-
The
|
|
67
|
+
The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions.
|
|
68
|
+
|
|
69
|
+
Additionally the EdDSA algorithm is supported via a [separate gem](https://rubygems.org/gems/jwt-eddsa).
|
|
70
|
+
|
|
71
|
+
For safe cryptographic signing, you need to specify the algorithm in the options hash whenever you call `JWT.decode` to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm**
|
|
43
72
|
|
|
44
73
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
|
45
74
|
|
|
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
|
+
```
|
|
91
|
+
|
|
46
92
|
### **NONE**
|
|
47
93
|
|
|
48
94
|
* none - unsigned token
|
|
49
95
|
|
|
50
96
|
```ruby
|
|
51
|
-
require 'jwt'
|
|
52
97
|
|
|
53
98
|
payload = { data: 'test' }
|
|
54
99
|
|
|
55
100
|
# IMPORTANT: set nil as password parameter
|
|
56
|
-
token = JWT.encode
|
|
101
|
+
token = JWT.encode(payload, nil, 'none')
|
|
57
102
|
|
|
58
103
|
# eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
|
|
59
104
|
puts token
|
|
60
105
|
|
|
61
106
|
# Set password to nil and validation to false otherwise this won't work
|
|
62
|
-
decoded_token = JWT.decode
|
|
107
|
+
decoded_token = JWT.decode(token, nil, false)
|
|
63
108
|
|
|
64
109
|
# Array
|
|
65
110
|
# [
|
|
@@ -72,7 +117,6 @@ puts decoded_token
|
|
|
72
117
|
### **HMAC**
|
|
73
118
|
|
|
74
119
|
* HS256 - HMAC using SHA-256 hash algorithm
|
|
75
|
-
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
|
76
120
|
* HS384 - HMAC using SHA-384 hash algorithm
|
|
77
121
|
* HS512 - HMAC using SHA-512 hash algorithm
|
|
78
122
|
|
|
@@ -80,12 +124,12 @@ puts decoded_token
|
|
|
80
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.
|
|
81
125
|
hmac_secret = 'my$ecretK3y'
|
|
82
126
|
|
|
83
|
-
token = JWT.encode
|
|
127
|
+
token = JWT.encode(payload, hmac_secret, 'HS256')
|
|
84
128
|
|
|
85
129
|
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
|
|
86
130
|
puts token
|
|
87
131
|
|
|
88
|
-
decoded_token = JWT.decode
|
|
132
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
|
89
133
|
|
|
90
134
|
# Array
|
|
91
135
|
# [
|
|
@@ -95,12 +139,6 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
|
|
95
139
|
puts decoded_token
|
|
96
140
|
```
|
|
97
141
|
|
|
98
|
-
Note: If [RbNaCl](https://github.com/RubyCrypto/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl prior to 6.0.0 only support a maximum key size of 32 bytes for these algorithms.
|
|
99
|
-
|
|
100
|
-
[RbNaCl](https://github.com/RubyCrypto/rbnacl) requires
|
|
101
|
-
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
|
102
|
-
on MacOS with `brew install libsodium`.
|
|
103
|
-
|
|
104
142
|
### **RSA**
|
|
105
143
|
|
|
106
144
|
* RS256 - RSA using SHA-256 hash algorithm
|
|
@@ -108,15 +146,15 @@ on MacOS with `brew install libsodium`.
|
|
|
108
146
|
* RS512 - RSA using SHA-512 hash algorithm
|
|
109
147
|
|
|
110
148
|
```ruby
|
|
111
|
-
rsa_private = OpenSSL::PKey::RSA.generate
|
|
149
|
+
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
|
112
150
|
rsa_public = rsa_private.public_key
|
|
113
151
|
|
|
114
|
-
token = JWT.encode
|
|
152
|
+
token = JWT.encode(payload, rsa_private, 'RS256')
|
|
115
153
|
|
|
116
154
|
# eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
|
|
117
155
|
puts token
|
|
118
156
|
|
|
119
|
-
decoded_token = JWT.decode
|
|
157
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
|
|
120
158
|
|
|
121
159
|
# Array
|
|
122
160
|
# [
|
|
@@ -136,12 +174,12 @@ puts decoded_token
|
|
|
136
174
|
```ruby
|
|
137
175
|
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
|
138
176
|
|
|
139
|
-
token = JWT.encode
|
|
177
|
+
token = JWT.encode(payload, ecdsa_key, 'ES256')
|
|
140
178
|
|
|
141
179
|
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
|
142
180
|
puts token
|
|
143
181
|
|
|
144
|
-
decoded_token = JWT.decode
|
|
182
|
+
decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
|
|
145
183
|
|
|
146
184
|
# Array
|
|
147
185
|
# [
|
|
@@ -196,12 +234,12 @@ gem 'openssl', '~> 2.1'
|
|
|
196
234
|
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
|
197
235
|
rsa_public = rsa_private.public_key
|
|
198
236
|
|
|
199
|
-
token = JWT.encode
|
|
237
|
+
token = JWT.encode(payload, rsa_private, 'PS256')
|
|
200
238
|
|
|
201
239
|
# eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
|
|
202
240
|
puts token
|
|
203
241
|
|
|
204
|
-
decoded_token = JWT.decode
|
|
242
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
|
|
205
243
|
|
|
206
244
|
# Array
|
|
207
245
|
# [
|
|
@@ -211,22 +249,57 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
|
|
|
211
249
|
puts decoded_token
|
|
212
250
|
```
|
|
213
251
|
|
|
252
|
+
### Add custom header fields
|
|
253
|
+
Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
|
|
254
|
+
To add custom header fields you need to pass `header_fields` parameter
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
token = JWT.encode(payload, key, algorithm='HS256', header_fields={})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Example:**
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
|
|
264
|
+
payload = { data: 'test' }
|
|
265
|
+
|
|
266
|
+
# IMPORTANT: set nil as password parameter
|
|
267
|
+
token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
|
|
268
|
+
|
|
269
|
+
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
|
|
270
|
+
puts token
|
|
271
|
+
|
|
272
|
+
# Set password to nil and validation to false otherwise this won't work
|
|
273
|
+
decoded_token = JWT.decode(token, nil, false)
|
|
274
|
+
|
|
275
|
+
# Array
|
|
276
|
+
# [
|
|
277
|
+
# {"data"=>"test"}, # payload
|
|
278
|
+
# {"typ"=>"JWT", "alg"=>"none"} # header
|
|
279
|
+
# ]
|
|
280
|
+
puts decoded_token
|
|
281
|
+
```
|
|
282
|
+
|
|
214
283
|
### **Custom algorithms**
|
|
215
284
|
|
|
216
|
-
|
|
285
|
+
When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods:
|
|
286
|
+
|
|
287
|
+
- For decoding/verifying: The object must implement the methods `alg` and `verify`.
|
|
288
|
+
- For encoding/signing: The object must implement the methods `alg` and `sign`.
|
|
289
|
+
|
|
290
|
+
For customization options check the details from `JWT::JWA::SigningAlgorithm`.
|
|
291
|
+
|
|
217
292
|
|
|
218
293
|
```ruby
|
|
219
294
|
module CustomHS512Algorithm
|
|
295
|
+
extend JWT::JWA::SigningAlgorithm
|
|
296
|
+
|
|
220
297
|
def self.alg
|
|
221
298
|
'HS512'
|
|
222
299
|
end
|
|
223
300
|
|
|
224
|
-
def self.valid_alg?(alg_to_validate)
|
|
225
|
-
alg_to_validate == alg
|
|
226
|
-
end
|
|
227
|
-
|
|
228
301
|
def self.sign(data:, signing_key:)
|
|
229
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'),
|
|
302
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
|
|
230
303
|
end
|
|
231
304
|
|
|
232
305
|
def self.verify(data:, signature:, verification_key:)
|
|
@@ -238,49 +311,63 @@ token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
|
|
|
238
311
|
payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
|
|
239
312
|
```
|
|
240
313
|
|
|
241
|
-
##
|
|
242
|
-
JSON Web Token defines some reserved claim names and defines how they should be
|
|
243
|
-
used. JWT supports these reserved claim names:
|
|
314
|
+
## `JWT::Token` and `JWT::EncodedToken`
|
|
244
315
|
|
|
245
|
-
|
|
246
|
-
- 'nbf' (Not Before Time) Claim
|
|
247
|
-
- 'iss' (Issuer) Claim
|
|
248
|
-
- 'aud' (Audience) Claim
|
|
249
|
-
- 'jti' (JWT ID) Claim
|
|
250
|
-
- 'iat' (Issued At) Claim
|
|
251
|
-
- 'sub' (Subject) Claim
|
|
316
|
+
The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
|
|
252
317
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
318
|
+
```ruby
|
|
319
|
+
token = JWT::Token.new(payload: { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }, header: { kid: 'hmac' })
|
|
320
|
+
token.sign!(algorithm: 'HS256', key: "secret")
|
|
256
321
|
|
|
322
|
+
token.jwt # => "eyJhbGciOiJIUzI1N..."
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
The `JWT::EncodedToken` can be used to create a token object that allows verification of signatures and claims
|
|
257
326
|
```ruby
|
|
258
|
-
|
|
327
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
|
328
|
+
|
|
329
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
|
|
330
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
|
|
331
|
+
encoded_token.verify_claims!(:exp, :jti)
|
|
332
|
+
encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
|
|
333
|
+
encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
|
|
334
|
+
encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
|
|
335
|
+
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
|
|
259
336
|
```
|
|
260
337
|
|
|
261
|
-
|
|
338
|
+
### Detached payload
|
|
339
|
+
|
|
340
|
+
The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
|
|
262
341
|
|
|
263
342
|
```ruby
|
|
264
|
-
|
|
343
|
+
token = JWT::Token.new(payload: { pay: 'load' })
|
|
344
|
+
token.sign!(algorithm: 'HS256', key: "secret")
|
|
345
|
+
token.detach_payload!
|
|
346
|
+
token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
|
|
347
|
+
token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
|
|
348
|
+
```
|
|
265
349
|
|
|
266
|
-
payload
|
|
350
|
+
The `JWT::EncodedToken` class can be used to decode a token with a detached payload by providing the payload to the token instance in separate.
|
|
267
351
|
|
|
268
|
-
|
|
269
|
-
|
|
352
|
+
```ruby
|
|
353
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
|
354
|
+
encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
|
|
355
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
|
|
356
|
+
encoded_token.payload # => {"pay"=>"load"}
|
|
357
|
+
```
|
|
270
358
|
|
|
271
|
-
|
|
272
|
-
puts token
|
|
359
|
+
## Claims
|
|
273
360
|
|
|
274
|
-
|
|
275
|
-
|
|
361
|
+
JSON Web Token defines some reserved claim names and defines how they should be
|
|
362
|
+
used. JWT supports these reserved claim names:
|
|
276
363
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
284
371
|
|
|
285
372
|
### Expiration Time Claim
|
|
286
373
|
|
|
@@ -294,10 +381,10 @@ From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.i
|
|
|
294
381
|
exp = Time.now.to_i + 4 * 3600
|
|
295
382
|
exp_payload = { data: 'data', exp: exp }
|
|
296
383
|
|
|
297
|
-
token = JWT.encode
|
|
384
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
|
298
385
|
|
|
299
386
|
begin
|
|
300
|
-
decoded_token = JWT.decode
|
|
387
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
|
301
388
|
rescue JWT::ExpiredSignature
|
|
302
389
|
# Handle expired token, e.g. logout user or deny access
|
|
303
390
|
end
|
|
@@ -306,7 +393,7 @@ end
|
|
|
306
393
|
The Expiration Claim verification can be disabled.
|
|
307
394
|
```ruby
|
|
308
395
|
# Decode token without raising JWT::ExpiredSignature error
|
|
309
|
-
JWT.decode
|
|
396
|
+
JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
|
|
310
397
|
```
|
|
311
398
|
|
|
312
399
|
**Adding Leeway**
|
|
@@ -318,11 +405,11 @@ leeway = 30 # seconds
|
|
|
318
405
|
exp_payload = { data: 'data', exp: exp }
|
|
319
406
|
|
|
320
407
|
# build expired token
|
|
321
|
-
token = JWT.encode
|
|
408
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
|
322
409
|
|
|
323
410
|
begin
|
|
324
411
|
# add leeway to ensure the token is still accepted
|
|
325
|
-
decoded_token = JWT.decode
|
|
412
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
|
|
326
413
|
rescue JWT::ExpiredSignature
|
|
327
414
|
# Handle expired token, e.g. logout user or deny access
|
|
328
415
|
end
|
|
@@ -340,10 +427,10 @@ From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.o
|
|
|
340
427
|
nbf = Time.now.to_i - 3600
|
|
341
428
|
nbf_payload = { data: 'data', nbf: nbf }
|
|
342
429
|
|
|
343
|
-
token = JWT.encode
|
|
430
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
|
344
431
|
|
|
345
432
|
begin
|
|
346
|
-
decoded_token = JWT.decode
|
|
433
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
|
347
434
|
rescue JWT::ImmatureSignature
|
|
348
435
|
# Handle invalid token, e.g. logout user or deny access
|
|
349
436
|
end
|
|
@@ -352,7 +439,7 @@ end
|
|
|
352
439
|
The Not Before Claim verification can be disabled.
|
|
353
440
|
```ruby
|
|
354
441
|
# Decode token without raising JWT::ImmatureSignature error
|
|
355
|
-
JWT.decode
|
|
442
|
+
JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
|
|
356
443
|
```
|
|
357
444
|
|
|
358
445
|
**Adding Leeway**
|
|
@@ -364,11 +451,11 @@ leeway = 30
|
|
|
364
451
|
nbf_payload = { data: 'data', nbf: nbf }
|
|
365
452
|
|
|
366
453
|
# build expired token
|
|
367
|
-
token = JWT.encode
|
|
454
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
|
368
455
|
|
|
369
456
|
begin
|
|
370
457
|
# add leeway to ensure the token is valid
|
|
371
|
-
decoded_token = JWT.decode
|
|
458
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
|
|
372
459
|
rescue JWT::ImmatureSignature
|
|
373
460
|
# Handle invalid token, e.g. logout user or deny access
|
|
374
461
|
end
|
|
@@ -386,11 +473,11 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
|
|
|
386
473
|
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
|
|
387
474
|
iss_payload = { data: 'data', iss: iss }
|
|
388
475
|
|
|
389
|
-
token = JWT.encode
|
|
476
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
|
390
477
|
|
|
391
478
|
begin
|
|
392
479
|
# Add iss to the validation to check if the token has been manipulated
|
|
393
|
-
decoded_token = JWT.decode
|
|
480
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
|
|
394
481
|
rescue JWT::InvalidIssuerError
|
|
395
482
|
# Handle invalid token, e.g. logout user or deny access
|
|
396
483
|
end
|
|
@@ -401,24 +488,24 @@ On supported ruby versions (>= 2.5) you can also delegate to methods, on older v
|
|
|
401
488
|
to convert them to proc (using `to_proc`)
|
|
402
489
|
|
|
403
490
|
```ruby
|
|
404
|
-
JWT.decode
|
|
491
|
+
JWT.decode(token, hmac_secret, true,
|
|
405
492
|
iss: %r'https://my.awesome.website/',
|
|
406
493
|
verify_iss: true,
|
|
407
|
-
algorithm: 'HS256'
|
|
494
|
+
algorithm: 'HS256')
|
|
408
495
|
```
|
|
409
496
|
|
|
410
497
|
```ruby
|
|
411
|
-
JWT.decode
|
|
498
|
+
JWT.decode(token, hmac_secret, true,
|
|
412
499
|
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
|
413
500
|
verify_iss: true,
|
|
414
|
-
algorithm: 'HS256'
|
|
501
|
+
algorithm: 'HS256')
|
|
415
502
|
```
|
|
416
503
|
|
|
417
504
|
```ruby
|
|
418
|
-
JWT.decode
|
|
505
|
+
JWT.decode(token, hmac_secret, true,
|
|
419
506
|
iss: method(:valid_issuer?),
|
|
420
507
|
verify_iss: true,
|
|
421
|
-
algorithm: 'HS256'
|
|
508
|
+
algorithm: 'HS256')
|
|
422
509
|
|
|
423
510
|
# somewhere in the same class:
|
|
424
511
|
def valid_issuer?(issuer)
|
|
@@ -436,11 +523,11 @@ From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org
|
|
|
436
523
|
aud = ['Young', 'Old']
|
|
437
524
|
aud_payload = { data: 'data', aud: aud }
|
|
438
525
|
|
|
439
|
-
token = JWT.encode
|
|
526
|
+
token = JWT.encode(aud_payload, hmac_secret, 'HS256')
|
|
440
527
|
|
|
441
528
|
begin
|
|
442
529
|
# Add aud to the validation to check if the token has been manipulated
|
|
443
|
-
decoded_token = JWT.decode
|
|
530
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
|
|
444
531
|
rescue JWT::InvalidAudError
|
|
445
532
|
# Handle invalid token, e.g. logout user or deny access
|
|
446
533
|
puts 'Audience Error'
|
|
@@ -459,15 +546,15 @@ jti_raw = [hmac_secret, iat].join(':').to_s
|
|
|
459
546
|
jti = Digest::MD5.hexdigest(jti_raw)
|
|
460
547
|
jti_payload = { data: 'data', iat: iat, jti: jti }
|
|
461
548
|
|
|
462
|
-
token = JWT.encode
|
|
549
|
+
token = JWT.encode(jti_payload, hmac_secret, 'HS256')
|
|
463
550
|
|
|
464
551
|
begin
|
|
465
552
|
# If :verify_jti is true, validation will pass if a JTI is present
|
|
466
|
-
#decoded_token = JWT.decode
|
|
553
|
+
#decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
|
|
467
554
|
# Alternatively, pass a proc with your own code to check if the JTI has already been used
|
|
468
|
-
decoded_token = JWT.decode
|
|
555
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
|
|
469
556
|
# or
|
|
470
|
-
decoded_token = JWT.decode
|
|
557
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
|
|
471
558
|
rescue JWT::InvalidJtiError
|
|
472
559
|
# Handle invalid token, e.g. logout user or deny access
|
|
473
560
|
puts 'Error'
|
|
@@ -486,11 +573,11 @@ From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.or
|
|
|
486
573
|
iat = Time.now.to_i
|
|
487
574
|
iat_payload = { data: 'data', iat: iat }
|
|
488
575
|
|
|
489
|
-
token = JWT.encode
|
|
576
|
+
token = JWT.encode(iat_payload, hmac_secret, 'HS256')
|
|
490
577
|
|
|
491
578
|
begin
|
|
492
579
|
# Add iat to the validation to check if the token has been manipulated
|
|
493
|
-
decoded_token = JWT.decode
|
|
580
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
|
|
494
581
|
rescue JWT::InvalidIatError
|
|
495
582
|
# Handle invalid token, e.g. logout user or deny access
|
|
496
583
|
end
|
|
@@ -506,16 +593,32 @@ From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/
|
|
|
506
593
|
sub = 'Subject'
|
|
507
594
|
sub_payload = { data: 'data', sub: sub }
|
|
508
595
|
|
|
509
|
-
token = JWT.encode
|
|
596
|
+
token = JWT.encode(sub_payload, hmac_secret, 'HS256')
|
|
510
597
|
|
|
511
598
|
begin
|
|
512
599
|
# Add sub to the validation to check if the token has been manipulated
|
|
513
|
-
decoded_token = JWT.decode
|
|
600
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
|
|
514
601
|
rescue JWT::InvalidSubError
|
|
515
602
|
# Handle invalid token, e.g. logout user or deny access
|
|
516
603
|
end
|
|
517
604
|
```
|
|
518
605
|
|
|
606
|
+
### Standalone claim verification
|
|
607
|
+
|
|
608
|
+
The JWT claim verifications can be used to verify any Hash to include expected keys and values.
|
|
609
|
+
|
|
610
|
+
A few example on verifying the claims for a payload:
|
|
611
|
+
```ruby
|
|
612
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
|
|
613
|
+
JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
|
|
614
|
+
# => true
|
|
615
|
+
JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp)
|
|
616
|
+
# => [#<struct JWT::Claims::Error message="Signature has expired">]
|
|
617
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
|
|
618
|
+
|
|
619
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
|
|
620
|
+
```
|
|
621
|
+
|
|
519
622
|
### Finding a Key
|
|
520
623
|
|
|
521
624
|
To dynamically find the key for verifying the JWT signature, pass a block to the decode block. The block receives headers and the original payload as parameters. It should return with the key to verify the signature that was used to sign the JWT.
|
|
@@ -526,7 +629,7 @@ iss_payload = { data: 'data', iss: issuers.first }
|
|
|
526
629
|
|
|
527
630
|
secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
|
|
528
631
|
|
|
529
|
-
token = JWT.encode
|
|
632
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
|
530
633
|
|
|
531
634
|
begin
|
|
532
635
|
# Add iss to the validation to check if the token has been manipulated
|
|
@@ -543,7 +646,7 @@ end
|
|
|
543
646
|
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
|
544
647
|
```ruby
|
|
545
648
|
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
|
546
|
-
JWT.decode
|
|
649
|
+
JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
|
|
547
650
|
```
|
|
548
651
|
|
|
549
652
|
### X.509 certificates in x5c header
|
|
@@ -561,13 +664,13 @@ crls = crl_uris.map do |uri|
|
|
|
561
664
|
end
|
|
562
665
|
|
|
563
666
|
begin
|
|
564
|
-
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
|
|
667
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
|
|
565
668
|
rescue JWT::DecodeError
|
|
566
669
|
# Handle error, e.g. x5c header certificate revoked or expired
|
|
567
670
|
end
|
|
568
671
|
```
|
|
569
672
|
|
|
570
|
-
|
|
673
|
+
## JSON Web Key (JWK)
|
|
571
674
|
|
|
572
675
|
JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve.
|
|
573
676
|
|
|
@@ -594,7 +697,6 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
|
|
|
594
697
|
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
|
|
595
698
|
```
|
|
596
699
|
|
|
597
|
-
|
|
598
700
|
The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
|
|
599
701
|
This can be used to implement caching of remotely fetched JWK Sets.
|
|
600
702
|
|
|
@@ -680,21 +782,25 @@ jwk_hash = jwk.export
|
|
|
680
782
|
thumbprint_as_the_kid = jwk_hash[:kid]
|
|
681
783
|
```
|
|
682
784
|
|
|
683
|
-
# Development and
|
|
785
|
+
# Development and testing
|
|
684
786
|
|
|
685
|
-
|
|
787
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
|
686
788
|
|
|
687
789
|
```bash
|
|
688
|
-
|
|
790
|
+
bundle install
|
|
791
|
+
bundle exec appraisal rake test
|
|
689
792
|
```
|
|
690
793
|
|
|
691
|
-
|
|
794
|
+
# Releasing
|
|
795
|
+
|
|
796
|
+
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:
|
|
692
797
|
|
|
693
798
|
```bash
|
|
694
|
-
|
|
695
|
-
bundle exec appraisal rake test
|
|
799
|
+
rake release:source_control_push
|
|
696
800
|
```
|
|
697
801
|
|
|
802
|
+
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
|
+
|
|
698
804
|
## How to contribute
|
|
699
805
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
700
806
|
|
data/lib/jwt/base64.rb
CHANGED
|
@@ -3,14 +3,31 @@
|
|
|
3
3
|
require 'base64'
|
|
4
4
|
|
|
5
5
|
module JWT
|
|
6
|
-
# Base64
|
|
6
|
+
# Base64 encoding and decoding
|
|
7
|
+
# @api private
|
|
7
8
|
class Base64
|
|
8
9
|
class << self
|
|
10
|
+
# Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).
|
|
11
|
+
# @api private
|
|
9
12
|
def url_encode(str)
|
|
10
|
-
::Base64.
|
|
13
|
+
::Base64.urlsafe_encode64(str, padding: false)
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Decode a string with URL-safe Base64 complying with RFC 4648.
|
|
17
|
+
# Deprecated support for RFC 2045 remains for now. ("All line breaks or other characters not found in Table 1 must be ignored by decoding software")
|
|
18
|
+
# @api private
|
|
13
19
|
def url_decode(str)
|
|
20
|
+
::Base64.urlsafe_decode64(str)
|
|
21
|
+
rescue ArgumentError => e
|
|
22
|
+
raise unless e.message == 'invalid base64'
|
|
23
|
+
raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding
|
|
24
|
+
|
|
25
|
+
loose_urlsafe_decode64(str).tap do
|
|
26
|
+
Deprecations.warning('Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt', only_if_valid: true)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def loose_urlsafe_decode64(str)
|
|
14
31
|
str += '=' * (4 - str.length.modulo(4))
|
|
15
32
|
::Base64.decode64(str.tr('-_', '+/'))
|
|
16
33
|
end
|