jwt 2.3.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AUTHORS +60 -53
- data/CHANGELOG.md +194 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/README.md +360 -106
- 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 +7 -24
- data/lib/jwt/configuration/container.rb +52 -0
- data/lib/jwt/configuration/decode_configuration.rb +70 -0
- data/lib/jwt/configuration/jwk_configuration.rb +28 -0
- data/lib/jwt/configuration.rb +23 -0
- data/lib/jwt/decode.rb +70 -61
- data/lib/jwt/deprecations.rb +49 -0
- data/lib/jwt/encode.rb +18 -57
- data/lib/jwt/encoded_token.rb +139 -0
- data/lib/jwt/error.rb +36 -0
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/compat.rb +32 -0
- data/lib/jwt/jwa/ecdsa.rb +90 -0
- data/lib/jwt/jwa/eddsa.rb +35 -0
- data/lib/jwt/jwa/hmac.rb +82 -0
- 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 +163 -63
- data/lib/jwt/jwk/hmac.rb +68 -24
- data/lib/jwt/jwk/key_base.rb +46 -6
- data/lib/jwt/jwk/key_finder.rb +20 -35
- data/lib/jwt/jwk/kid_as_key_digest.rb +16 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +109 -0
- data/lib/jwt/jwk/rsa.rb +141 -54
- data/lib/jwt/jwk/set.rb +82 -0
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +16 -11
- data/lib/jwt/token.rb +112 -0
- data/lib/jwt/verify.rb +16 -81
- data/lib/jwt/version.rb +53 -11
- data/lib/jwt/x5c_key_finder.rb +52 -0
- data/lib/jwt.rb +28 -4
- data/ruby-jwt.gemspec +15 -5
- metadata +75 -28
- data/.github/workflows/test.yml +0 -74
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -97
- data/.rubocop_todo.yml +0 -185
- data/.sourcelevel.yml +0 -18
- data/Appraisals +0 -10
- data/Gemfile +0 -5
- data/Rakefile +0 -14
- data/lib/jwt/algos/ecdsa.rb +0 -35
- data/lib/jwt/algos/eddsa.rb +0 -30
- data/lib/jwt/algos/hmac.rb +0 -34
- data/lib/jwt/algos/none.rb +0 -15
- data/lib/jwt/algos/ps.rb +0 -43
- data/lib/jwt/algos/rsa.rb +0 -19
- data/lib/jwt/algos/unsupported.rb +0 -17
- data/lib/jwt/algos.rb +0 -44
- data/lib/jwt/default_options.rb +0 -16
- data/lib/jwt/security_utils.rb +0 -57
- data/lib/jwt/signature.rb +0 -39
data/README.md
CHANGED
@@ -1,20 +1,38 @@
|
|
1
1
|
# JWT
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/jwt)
|
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)
|
8
|
-
[](https://app.sourcelevel.io/github/jwt/-/ruby-jwt)
|
9
8
|
|
10
9
|
A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
|
11
10
|
|
12
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).
|
13
12
|
|
14
|
-
|
13
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
15
14
|
|
16
|
-
|
17
|
-
|
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.
|
18
36
|
|
19
37
|
## Sponsors
|
20
38
|
|
@@ -25,40 +43,68 @@ If you have further questions related to development or usage, join us: [ruby-jw
|
|
25
43
|
## Installing
|
26
44
|
|
27
45
|
### Using Rubygems:
|
46
|
+
|
28
47
|
```bash
|
29
48
|
gem install jwt
|
30
49
|
```
|
31
50
|
|
32
51
|
### Using Bundler:
|
52
|
+
|
33
53
|
Add the following to your Gemfile
|
34
54
|
```
|
35
55
|
gem 'jwt'
|
36
56
|
```
|
57
|
+
|
37
58
|
And run `bundle install`
|
38
59
|
|
60
|
+
Finally require the gem in your application
|
61
|
+
```ruby
|
62
|
+
require 'jwt'
|
63
|
+
```
|
64
|
+
|
39
65
|
## Algorithms and Usage
|
40
66
|
|
41
|
-
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**
|
42
72
|
|
43
73
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
44
74
|
|
45
|
-
|
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
|
+
|
92
|
+
### **NONE**
|
46
93
|
|
47
94
|
* none - unsigned token
|
48
95
|
|
49
96
|
```ruby
|
50
|
-
require 'jwt'
|
51
97
|
|
52
98
|
payload = { data: 'test' }
|
53
99
|
|
54
100
|
# IMPORTANT: set nil as password parameter
|
55
|
-
token = JWT.encode
|
101
|
+
token = JWT.encode(payload, nil, 'none')
|
56
102
|
|
57
103
|
# eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
|
58
104
|
puts token
|
59
105
|
|
60
106
|
# Set password to nil and validation to false otherwise this won't work
|
61
|
-
decoded_token = JWT.decode
|
107
|
+
decoded_token = JWT.decode(token, nil, false)
|
62
108
|
|
63
109
|
# Array
|
64
110
|
# [
|
@@ -68,23 +114,22 @@ decoded_token = JWT.decode token, nil, false
|
|
68
114
|
puts decoded_token
|
69
115
|
```
|
70
116
|
|
71
|
-
**HMAC**
|
117
|
+
### **HMAC**
|
72
118
|
|
73
119
|
* HS256 - HMAC using SHA-256 hash algorithm
|
74
|
-
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
75
120
|
* HS384 - HMAC using SHA-384 hash algorithm
|
76
121
|
* HS512 - HMAC using SHA-512 hash algorithm
|
77
122
|
|
78
123
|
```ruby
|
79
|
-
# The secret must be a string.
|
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.
|
80
125
|
hmac_secret = 'my$ecretK3y'
|
81
126
|
|
82
|
-
token = JWT.encode
|
127
|
+
token = JWT.encode(payload, hmac_secret, 'HS256')
|
83
128
|
|
84
129
|
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
|
85
130
|
puts token
|
86
131
|
|
87
|
-
decoded_token = JWT.decode
|
132
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
88
133
|
|
89
134
|
# Array
|
90
135
|
# [
|
@@ -94,28 +139,22 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
|
94
139
|
puts decoded_token
|
95
140
|
```
|
96
141
|
|
97
|
-
|
98
|
-
|
99
|
-
[RbNaCl](https://github.com/cryptosphere/rbnacl) requires
|
100
|
-
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
101
|
-
on MacOS with `brew install libsodium`.
|
102
|
-
|
103
|
-
**RSA**
|
142
|
+
### **RSA**
|
104
143
|
|
105
144
|
* RS256 - RSA using SHA-256 hash algorithm
|
106
145
|
* RS384 - RSA using SHA-384 hash algorithm
|
107
146
|
* RS512 - RSA using SHA-512 hash algorithm
|
108
147
|
|
109
148
|
```ruby
|
110
|
-
rsa_private = OpenSSL::PKey::RSA.generate
|
149
|
+
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
111
150
|
rsa_public = rsa_private.public_key
|
112
151
|
|
113
|
-
token = JWT.encode
|
152
|
+
token = JWT.encode(payload, rsa_private, 'RS256')
|
114
153
|
|
115
154
|
# eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
|
116
155
|
puts token
|
117
156
|
|
118
|
-
decoded_token = JWT.decode
|
157
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
|
119
158
|
|
120
159
|
# Array
|
121
160
|
# [
|
@@ -125,24 +164,22 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
|
|
125
164
|
puts decoded_token
|
126
165
|
```
|
127
166
|
|
128
|
-
**ECDSA**
|
167
|
+
### **ECDSA**
|
129
168
|
|
130
169
|
* ES256 - ECDSA using P-256 and SHA-256
|
131
170
|
* ES384 - ECDSA using P-384 and SHA-384
|
132
171
|
* ES512 - ECDSA using P-521 and SHA-512
|
172
|
+
* ES256K - ECDSA using P-256K and SHA-256
|
133
173
|
|
134
174
|
```ruby
|
135
|
-
ecdsa_key = OpenSSL::PKey::EC.
|
136
|
-
ecdsa_key.generate_key
|
137
|
-
ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key
|
138
|
-
ecdsa_public.private_key = nil
|
175
|
+
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
139
176
|
|
140
|
-
token = JWT.encode
|
177
|
+
token = JWT.encode(payload, ecdsa_key, 'ES256')
|
141
178
|
|
142
179
|
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
143
180
|
puts token
|
144
181
|
|
145
|
-
decoded_token = JWT.decode
|
182
|
+
decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
|
146
183
|
|
147
184
|
# Array
|
148
185
|
# [
|
@@ -152,7 +189,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
|
|
152
189
|
puts decoded_token
|
153
190
|
```
|
154
191
|
|
155
|
-
**EDDSA**
|
192
|
+
### **EDDSA**
|
156
193
|
|
157
194
|
In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
|
158
195
|
|
@@ -160,7 +197,7 @@ In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`
|
|
160
197
|
gem 'rbnacl'
|
161
198
|
```
|
162
199
|
|
163
|
-
For more detailed installation instruction check the official [repository](https://github.com/
|
200
|
+
For more detailed installation instruction check the official [repository](https://github.com/RubyCrypto/rbnacl) on GitHub.
|
164
201
|
|
165
202
|
* ED25519
|
166
203
|
|
@@ -181,9 +218,9 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
|
181
218
|
|
182
219
|
```
|
183
220
|
|
184
|
-
**RSASSA-PSS**
|
221
|
+
### **RSASSA-PSS**
|
185
222
|
|
186
|
-
In order to use this algorithm you need to add the `openssl` gem to
|
223
|
+
In order to use this algorithm you need to add the `openssl` gem to your `Gemfile` with a version greater or equal to `2.1`.
|
187
224
|
|
188
225
|
```ruby
|
189
226
|
gem 'openssl', '~> 2.1'
|
@@ -197,12 +234,12 @@ gem 'openssl', '~> 2.1'
|
|
197
234
|
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
198
235
|
rsa_public = rsa_private.public_key
|
199
236
|
|
200
|
-
token = JWT.encode
|
237
|
+
token = JWT.encode(payload, rsa_private, 'PS256')
|
201
238
|
|
202
239
|
# eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
|
203
240
|
puts token
|
204
241
|
|
205
|
-
decoded_token = JWT.decode
|
242
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
|
206
243
|
|
207
244
|
# Array
|
208
245
|
# [
|
@@ -212,41 +249,28 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
|
|
212
249
|
puts decoded_token
|
213
250
|
```
|
214
251
|
|
215
|
-
|
216
|
-
JSON Web Token defines some reserved claim names and defines how they should be
|
217
|
-
used. JWT supports these reserved claim names:
|
218
|
-
|
219
|
-
- 'exp' (Expiration Time) Claim
|
220
|
-
- 'nbf' (Not Before Time) Claim
|
221
|
-
- 'iss' (Issuer) Claim
|
222
|
-
- 'aud' (Audience) Claim
|
223
|
-
- 'jti' (JWT ID) Claim
|
224
|
-
- 'iat' (Issued At) Claim
|
225
|
-
- 'sub' (Subject) Claim
|
226
|
-
|
227
|
-
## Add custom header fields
|
252
|
+
### Add custom header fields
|
228
253
|
Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
|
229
254
|
To add custom header fields you need to pass `header_fields` parameter
|
230
255
|
|
231
256
|
```ruby
|
232
|
-
token = JWT.encode
|
257
|
+
token = JWT.encode(payload, key, algorithm='HS256', header_fields={})
|
233
258
|
```
|
234
259
|
|
235
260
|
**Example:**
|
236
261
|
|
237
262
|
```ruby
|
238
|
-
require 'jwt'
|
239
263
|
|
240
264
|
payload = { data: 'test' }
|
241
265
|
|
242
266
|
# IMPORTANT: set nil as password parameter
|
243
|
-
token = JWT.encode
|
267
|
+
token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
|
244
268
|
|
245
269
|
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
|
246
270
|
puts token
|
247
271
|
|
248
272
|
# Set password to nil and validation to false otherwise this won't work
|
249
|
-
decoded_token = JWT.decode
|
273
|
+
decoded_token = JWT.decode(token, nil, false)
|
250
274
|
|
251
275
|
# Array
|
252
276
|
# [
|
@@ -256,6 +280,95 @@ decoded_token = JWT.decode token, nil, false
|
|
256
280
|
puts decoded_token
|
257
281
|
```
|
258
282
|
|
283
|
+
### **Custom algorithms**
|
284
|
+
|
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
|
+
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
module CustomHS512Algorithm
|
295
|
+
extend JWT::JWA::SigningAlgorithm
|
296
|
+
|
297
|
+
def self.alg
|
298
|
+
'HS512'
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.sign(data:, signing_key:)
|
302
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
|
303
|
+
end
|
304
|
+
|
305
|
+
def self.verify(data:, signature:, verification_key:)
|
306
|
+
::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
|
311
|
+
payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
|
312
|
+
```
|
313
|
+
|
314
|
+
## `JWT::Token` and `JWT::EncodedToken`
|
315
|
+
|
316
|
+
The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
|
317
|
+
|
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")
|
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
|
326
|
+
```ruby
|
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'}
|
336
|
+
```
|
337
|
+
|
338
|
+
### Detached payload
|
339
|
+
|
340
|
+
The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
|
341
|
+
|
342
|
+
```ruby
|
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
|
+
```
|
349
|
+
|
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.
|
351
|
+
|
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
|
+
```
|
358
|
+
|
359
|
+
## Claims
|
360
|
+
|
361
|
+
JSON Web Token defines some reserved claim names and defines how they should be
|
362
|
+
used. JWT supports these reserved claim names:
|
363
|
+
|
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
|
371
|
+
|
259
372
|
### Expiration Time Claim
|
260
373
|
|
261
374
|
From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
|
@@ -268,10 +381,10 @@ From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.i
|
|
268
381
|
exp = Time.now.to_i + 4 * 3600
|
269
382
|
exp_payload = { data: 'data', exp: exp }
|
270
383
|
|
271
|
-
token = JWT.encode
|
384
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
272
385
|
|
273
386
|
begin
|
274
|
-
decoded_token = JWT.decode
|
387
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
275
388
|
rescue JWT::ExpiredSignature
|
276
389
|
# Handle expired token, e.g. logout user or deny access
|
277
390
|
end
|
@@ -280,7 +393,7 @@ end
|
|
280
393
|
The Expiration Claim verification can be disabled.
|
281
394
|
```ruby
|
282
395
|
# Decode token without raising JWT::ExpiredSignature error
|
283
|
-
JWT.decode
|
396
|
+
JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
|
284
397
|
```
|
285
398
|
|
286
399
|
**Adding Leeway**
|
@@ -292,11 +405,11 @@ leeway = 30 # seconds
|
|
292
405
|
exp_payload = { data: 'data', exp: exp }
|
293
406
|
|
294
407
|
# build expired token
|
295
|
-
token = JWT.encode
|
408
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
296
409
|
|
297
410
|
begin
|
298
411
|
# add leeway to ensure the token is still accepted
|
299
|
-
decoded_token = JWT.decode
|
412
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
|
300
413
|
rescue JWT::ExpiredSignature
|
301
414
|
# Handle expired token, e.g. logout user or deny access
|
302
415
|
end
|
@@ -314,10 +427,10 @@ From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.o
|
|
314
427
|
nbf = Time.now.to_i - 3600
|
315
428
|
nbf_payload = { data: 'data', nbf: nbf }
|
316
429
|
|
317
|
-
token = JWT.encode
|
430
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
318
431
|
|
319
432
|
begin
|
320
|
-
decoded_token = JWT.decode
|
433
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
321
434
|
rescue JWT::ImmatureSignature
|
322
435
|
# Handle invalid token, e.g. logout user or deny access
|
323
436
|
end
|
@@ -326,7 +439,7 @@ end
|
|
326
439
|
The Not Before Claim verification can be disabled.
|
327
440
|
```ruby
|
328
441
|
# Decode token without raising JWT::ImmatureSignature error
|
329
|
-
JWT.decode
|
442
|
+
JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
|
330
443
|
```
|
331
444
|
|
332
445
|
**Adding Leeway**
|
@@ -338,11 +451,11 @@ leeway = 30
|
|
338
451
|
nbf_payload = { data: 'data', nbf: nbf }
|
339
452
|
|
340
453
|
# build expired token
|
341
|
-
token = JWT.encode
|
454
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
342
455
|
|
343
456
|
begin
|
344
457
|
# add leeway to ensure the token is valid
|
345
|
-
decoded_token = JWT.decode
|
458
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
|
346
459
|
rescue JWT::ImmatureSignature
|
347
460
|
# Handle invalid token, e.g. logout user or deny access
|
348
461
|
end
|
@@ -360,16 +473,46 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
|
|
360
473
|
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
|
361
474
|
iss_payload = { data: 'data', iss: iss }
|
362
475
|
|
363
|
-
token = JWT.encode
|
476
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
364
477
|
|
365
478
|
begin
|
366
479
|
# Add iss to the validation to check if the token has been manipulated
|
367
|
-
decoded_token = JWT.decode
|
480
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
|
368
481
|
rescue JWT::InvalidIssuerError
|
369
482
|
# Handle invalid token, e.g. logout user or deny access
|
370
483
|
end
|
371
484
|
```
|
372
485
|
|
486
|
+
You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy.
|
487
|
+
On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have
|
488
|
+
to convert them to proc (using `to_proc`)
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
JWT.decode(token, hmac_secret, true,
|
492
|
+
iss: %r'https://my.awesome.website/',
|
493
|
+
verify_iss: true,
|
494
|
+
algorithm: 'HS256')
|
495
|
+
```
|
496
|
+
|
497
|
+
```ruby
|
498
|
+
JWT.decode(token, hmac_secret, true,
|
499
|
+
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
500
|
+
verify_iss: true,
|
501
|
+
algorithm: 'HS256')
|
502
|
+
```
|
503
|
+
|
504
|
+
```ruby
|
505
|
+
JWT.decode(token, hmac_secret, true,
|
506
|
+
iss: method(:valid_issuer?),
|
507
|
+
verify_iss: true,
|
508
|
+
algorithm: 'HS256')
|
509
|
+
|
510
|
+
# somewhere in the same class:
|
511
|
+
def valid_issuer?(issuer)
|
512
|
+
# custom validation
|
513
|
+
end
|
514
|
+
```
|
515
|
+
|
373
516
|
### Audience Claim
|
374
517
|
|
375
518
|
From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
|
@@ -380,11 +523,11 @@ From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org
|
|
380
523
|
aud = ['Young', 'Old']
|
381
524
|
aud_payload = { data: 'data', aud: aud }
|
382
525
|
|
383
|
-
token = JWT.encode
|
526
|
+
token = JWT.encode(aud_payload, hmac_secret, 'HS256')
|
384
527
|
|
385
528
|
begin
|
386
529
|
# Add aud to the validation to check if the token has been manipulated
|
387
|
-
decoded_token = JWT.decode
|
530
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
|
388
531
|
rescue JWT::InvalidAudError
|
389
532
|
# Handle invalid token, e.g. logout user or deny access
|
390
533
|
puts 'Audience Error'
|
@@ -403,15 +546,15 @@ jti_raw = [hmac_secret, iat].join(':').to_s
|
|
403
546
|
jti = Digest::MD5.hexdigest(jti_raw)
|
404
547
|
jti_payload = { data: 'data', iat: iat, jti: jti }
|
405
548
|
|
406
|
-
token = JWT.encode
|
549
|
+
token = JWT.encode(jti_payload, hmac_secret, 'HS256')
|
407
550
|
|
408
551
|
begin
|
409
552
|
# If :verify_jti is true, validation will pass if a JTI is present
|
410
|
-
#decoded_token = JWT.decode
|
553
|
+
#decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
|
411
554
|
# Alternatively, pass a proc with your own code to check if the JTI has already been used
|
412
|
-
decoded_token = JWT.decode
|
555
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
|
413
556
|
# or
|
414
|
-
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' })
|
415
558
|
rescue JWT::InvalidJtiError
|
416
559
|
# Handle invalid token, e.g. logout user or deny access
|
417
560
|
puts 'Error'
|
@@ -430,11 +573,11 @@ From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.or
|
|
430
573
|
iat = Time.now.to_i
|
431
574
|
iat_payload = { data: 'data', iat: iat }
|
432
575
|
|
433
|
-
token = JWT.encode
|
576
|
+
token = JWT.encode(iat_payload, hmac_secret, 'HS256')
|
434
577
|
|
435
578
|
begin
|
436
579
|
# Add iat to the validation to check if the token has been manipulated
|
437
|
-
decoded_token = JWT.decode
|
580
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
|
438
581
|
rescue JWT::InvalidIatError
|
439
582
|
# Handle invalid token, e.g. logout user or deny access
|
440
583
|
end
|
@@ -450,16 +593,32 @@ From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/
|
|
450
593
|
sub = 'Subject'
|
451
594
|
sub_payload = { data: 'data', sub: sub }
|
452
595
|
|
453
|
-
token = JWT.encode
|
596
|
+
token = JWT.encode(sub_payload, hmac_secret, 'HS256')
|
454
597
|
|
455
598
|
begin
|
456
599
|
# Add sub to the validation to check if the token has been manipulated
|
457
|
-
decoded_token = JWT.decode
|
600
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
|
458
601
|
rescue JWT::InvalidSubError
|
459
602
|
# Handle invalid token, e.g. logout user or deny access
|
460
603
|
end
|
461
604
|
```
|
462
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
|
+
|
463
622
|
### Finding a Key
|
464
623
|
|
465
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.
|
@@ -470,7 +629,7 @@ iss_payload = { data: 'data', iss: issuers.first }
|
|
470
629
|
|
471
630
|
secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
|
472
631
|
|
473
|
-
token = JWT.encode
|
632
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
474
633
|
|
475
634
|
begin
|
476
635
|
# Add iss to the validation to check if the token has been manipulated
|
@@ -486,28 +645,88 @@ end
|
|
486
645
|
|
487
646
|
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
488
647
|
```ruby
|
489
|
-
# Will raise a JWT::
|
490
|
-
JWT.decode
|
648
|
+
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
649
|
+
JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
|
491
650
|
```
|
492
651
|
|
493
|
-
###
|
652
|
+
### X.509 certificates in x5c header
|
494
653
|
|
495
|
-
|
654
|
+
A JWT signature can be verified using certificate(s) given in the `x5c` header. Before doing that, the trustworthiness of these certificate(s) must be established. This is done in accordance with RFC 5280 which (among other things) verifies the certificate(s) are issued by a trusted root certificate, the timestamps are valid, and none of the certificate(s) are revoked (i.e. being present in the root certificate's Certificate Revocation List).
|
496
655
|
|
497
656
|
```ruby
|
498
|
-
|
499
|
-
|
657
|
+
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
|
658
|
+
crl_uris = root_certificates.map(&:crl_uris)
|
659
|
+
crls = crl_uris.map do |uri|
|
660
|
+
# look up cached CRL by `uri` and return it if found, otherwise continue
|
661
|
+
crl = Net::HTTP.get(uri)
|
662
|
+
crl = OpenSSL::X509::CRL.new(crl)
|
663
|
+
# cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
|
664
|
+
end
|
665
|
+
|
666
|
+
begin
|
667
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
|
668
|
+
rescue JWT::DecodeError
|
669
|
+
# Handle error, e.g. x5c header certificate revoked or expired
|
670
|
+
end
|
671
|
+
```
|
672
|
+
|
673
|
+
## JSON Web Key (JWK)
|
500
674
|
|
501
|
-
|
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.
|
502
676
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
677
|
+
To encode a JWT using your JWK:
|
678
|
+
|
679
|
+
```ruby
|
680
|
+
optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
|
681
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
|
682
|
+
|
683
|
+
# Encoding
|
684
|
+
payload = { data: 'data' }
|
685
|
+
token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
|
686
|
+
|
687
|
+
# JSON Web Key Set for advertising your signing keys
|
688
|
+
jwks_hash = JWT::JWK::Set.new(jwk).export
|
689
|
+
```
|
690
|
+
|
691
|
+
To decode a JWT using a trusted entity's JSON Web Key Set (JWKS):
|
692
|
+
|
693
|
+
```ruby
|
694
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
695
|
+
jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
|
696
|
+
algorithms = jwks.map { |key| key[:alg] }.compact.uniq
|
697
|
+
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
|
698
|
+
```
|
699
|
+
|
700
|
+
The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
|
701
|
+
This can be used to implement caching of remotely fetched JWK Sets.
|
702
|
+
|
703
|
+
If the requested `kid` is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
|
704
|
+
The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
|
705
|
+
|
706
|
+
Tokens without a specified `kid` are rejected by default.
|
707
|
+
This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
|
708
|
+
|
709
|
+
```ruby
|
710
|
+
jwks_loader = ->(options) do
|
711
|
+
# The jwk loader would fetch the set of JWKs from a trusted source.
|
712
|
+
# To avoid malicious requests triggering cache invalidations there needs to be
|
713
|
+
# some kind of grace time or other logic for determining the validity of the invalidation.
|
714
|
+
# This example only allows cache invalidations every 5 minutes.
|
715
|
+
if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
|
716
|
+
logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
|
717
|
+
@cached_keys = nil
|
718
|
+
end
|
719
|
+
@cached_keys ||= begin
|
720
|
+
@cache_last_update = Time.now.to_i
|
721
|
+
# Replace with your own JWKS fetching routine
|
722
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
723
|
+
jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
|
724
|
+
jwks
|
725
|
+
end
|
507
726
|
end
|
508
727
|
|
509
728
|
begin
|
510
|
-
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks:
|
729
|
+
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
|
511
730
|
rescue JWT::JWKError
|
512
731
|
# Handle problems with the provided JWKs
|
513
732
|
rescue JWT::DecodeError
|
@@ -515,32 +734,56 @@ rescue JWT::DecodeError
|
|
515
734
|
end
|
516
735
|
```
|
517
736
|
|
518
|
-
or by passing JWK as a simple Hash
|
519
|
-
|
520
|
-
```
|
521
|
-
jwks = { keys: [{ ... }] } # keys accepts both of string and symbol
|
522
|
-
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
|
523
|
-
```
|
524
|
-
|
525
737
|
### Importing and exporting JSON Web Keys
|
526
738
|
|
527
|
-
The ::JWT::JWK class can be used to import
|
739
|
+
The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys
|
740
|
+
and export to either format with and without the private key included.
|
741
|
+
|
742
|
+
To include the private key in the export pass the `include_private` parameter to the export method.
|
528
743
|
|
529
744
|
```ruby
|
530
|
-
|
745
|
+
# Import a JWK Hash (showing an HMAC example)
|
746
|
+
jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
|
531
747
|
|
748
|
+
# Import an OpenSSL key
|
749
|
+
# You can optionally add descriptive parameters to the JWK
|
750
|
+
desc_params = { kid: 'my-kid', use: 'sig' }
|
751
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
|
752
|
+
|
753
|
+
# Export as JWK Hash (public key only by default)
|
532
754
|
jwk_hash = jwk.export
|
533
755
|
jwk_hash_with_private_key = jwk.export(include_private: true)
|
756
|
+
|
757
|
+
# Export as OpenSSL key
|
758
|
+
public_key = jwk.verify_key
|
759
|
+
private_key = jwk.signing_key if jwk.private?
|
760
|
+
|
761
|
+
# You can also import and export entire JSON Web Key Sets
|
762
|
+
jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
|
763
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
764
|
+
jwks_hash = jwks.export
|
534
765
|
```
|
535
766
|
|
536
|
-
|
767
|
+
### Key ID (kid) and JWKs
|
537
768
|
|
538
|
-
|
769
|
+
The key id (kid) generation in the gem is a custom algorithm and not based on any standards.
|
770
|
+
To use a standardized JWK thumbprint (RFC 7638) as the kid for JWKs a generator type can be specified in the global configuration
|
771
|
+
or can be given to the JWK instance on initialization.
|
539
772
|
|
540
|
-
```
|
541
|
-
|
773
|
+
```ruby
|
774
|
+
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
|
775
|
+
# OR
|
776
|
+
JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
|
777
|
+
# OR
|
778
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
|
779
|
+
|
780
|
+
jwk_hash = jwk.export
|
781
|
+
|
782
|
+
thumbprint_as_the_kid = jwk_hash[:kid]
|
542
783
|
```
|
543
784
|
|
785
|
+
# Development and testing
|
786
|
+
|
544
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.
|
545
788
|
|
546
789
|
```bash
|
@@ -548,12 +791,23 @@ bundle install
|
|
548
791
|
bundle exec appraisal rake test
|
549
792
|
```
|
550
793
|
|
551
|
-
|
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:
|
797
|
+
|
798
|
+
```bash
|
799
|
+
rake release:source_control_push
|
800
|
+
```
|
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
|
+
|
804
|
+
## How to contribute
|
805
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
552
806
|
|
553
807
|
## Contributors
|
554
808
|
|
555
|
-
See
|
809
|
+
See [AUTHORS](AUTHORS).
|
556
810
|
|
557
811
|
## License
|
558
812
|
|
559
|
-
See
|
813
|
+
See [LICENSE](LICENSE).
|