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