jwt 2.8.2 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -31
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +299 -234
- data/UPGRADING.md +47 -0
- data/lib/jwt/base64.rb +4 -10
- 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 +45 -0
- data/lib/jwt/claims/required.rb +33 -0
- data/lib/jwt/claims/subject.rb +30 -0
- data/lib/jwt/claims/verifier.rb +61 -0
- data/lib/jwt/claims.rb +67 -0
- data/lib/jwt/configuration/container.rb +20 -1
- 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 +42 -81
- data/lib/jwt/encode.rb +17 -60
- data/lib/jwt/encoded_token.rb +236 -0
- data/lib/jwt/error.rb +32 -1
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/ecdsa.rb +59 -24
- data/lib/jwt/jwa/hmac.rb +22 -19
- data/lib/jwt/jwa/none.rb +8 -3
- data/lib/jwt/jwa/ps.rb +21 -15
- data/lib/jwt/jwa/rsa.rb +21 -10
- data/lib/jwt/jwa/signing_algorithm.rb +62 -0
- data/lib/jwt/jwa/unsupported.rb +9 -8
- data/lib/jwt/jwa.rb +76 -35
- data/lib/jwt/jwk/ec.rb +54 -65
- data/lib/jwt/jwk/hmac.rb +5 -6
- data/lib/jwt/jwk/key_base.rb +16 -1
- data/lib/jwt/jwk/key_finder.rb +35 -8
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +7 -4
- data/lib/jwt/jwk/set.rb +2 -0
- data/lib/jwt/jwk.rb +1 -1
- data/lib/jwt/token.rb +131 -0
- data/lib/jwt/version.rb +24 -19
- data/lib/jwt.rb +18 -4
- data/ruby-jwt.gemspec +2 -0
- metadata +49 -15
- data/lib/jwt/claims_validator.rb +0 -37
- data/lib/jwt/deprecations.rb +0 -48
- data/lib/jwt/jwa/eddsa.rb +0 -42
- data/lib/jwt/jwa/hmac_rbnacl.rb +0 -50
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
- data/lib/jwt/jwa/wrapper.rb +0 -26
- data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
- data/lib/jwt/verify.rb +0 -117
data/README.md
CHANGED
@@ -1,325 +1,370 @@
|
|
1
1
|
# JWT
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/jwt)
|
4
|
-
[](https://github.com/jwt/ruby-jwt/actions)
|
5
|
-
[](https://codeclimate.com/github/jwt/ruby-jwt)
|
4
|
+
[](https://github.com/jwt/ruby-jwt/actions)
|
5
|
+
[](https://qlty.sh/gh/jwt/projects/ruby-jwt)
|
6
|
+
[](https://qlty.sh/gh/jwt/projects/ruby-jwt)
|
8
7
|
|
9
8
|
A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
|
10
9
|
|
11
10
|
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
12
11
|
|
13
|
-
|
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
|
-
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
12
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes and [upgrade guide](UPGRADING.md) for upgrading between major versions.
|
19
13
|
|
20
14
|
## Sponsors
|
21
15
|
|
22
|
-
|Logo|Message|
|
23
|
-
|
24
|
-
|
16
|
+
| Logo | Message |
|
17
|
+
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
18
|
+
|  | If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth) |
|
25
19
|
|
26
20
|
## Installing
|
27
21
|
|
28
|
-
### Using Rubygems
|
22
|
+
### Using Rubygems
|
23
|
+
|
29
24
|
```bash
|
30
25
|
gem install jwt
|
31
26
|
```
|
32
27
|
|
33
|
-
### Using Bundler
|
28
|
+
### Using Bundler
|
29
|
+
|
34
30
|
Add the following to your Gemfile
|
35
|
-
|
31
|
+
|
32
|
+
```bash
|
36
33
|
gem 'jwt'
|
37
34
|
```
|
38
|
-
And run `bundle install`
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cryptographic signing. Currently the jwt gem supports NONE, HMAC, RSASSA and ECDSA. If you are using 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
|
-
|
44
|
-
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
45
|
-
|
46
|
-
### Deprecation warnings
|
36
|
+
And run `bundle install`
|
47
37
|
|
48
|
-
|
38
|
+
Finally require the gem in your application
|
49
39
|
|
50
40
|
```ruby
|
51
|
-
|
41
|
+
require 'jwt'
|
52
42
|
```
|
53
43
|
|
54
|
-
|
44
|
+
## Algorithms and Usage
|
45
|
+
|
46
|
+
The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions.
|
55
47
|
|
56
|
-
|
48
|
+
Additionally the EdDSA algorithm is supported via a the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
```
|
50
|
+
For safe cryptographic signing, you need to specify the algorithm in the options hash whenever you call `JWT.decode` to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm**
|
51
|
+
|
52
|
+
See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
62
53
|
|
63
54
|
### **NONE**
|
64
55
|
|
65
|
-
|
56
|
+
- none - unsigned token
|
66
57
|
|
67
58
|
```ruby
|
68
|
-
require 'jwt'
|
69
|
-
|
70
59
|
payload = { data: 'test' }
|
60
|
+
token = JWT.encode(payload, nil, 'none')
|
61
|
+
# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
|
71
62
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
# Set password to nil and validation to false otherwise this won't work
|
79
|
-
decoded_token = JWT.decode token, nil, false
|
80
|
-
|
81
|
-
# Array
|
82
|
-
# [
|
83
|
-
# {"data"=>"test"}, # payload
|
84
|
-
# {"alg"=>"none"} # header
|
85
|
-
# ]
|
86
|
-
puts decoded_token
|
63
|
+
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
|
64
|
+
# => [
|
65
|
+
# {"data"=>"test"}, # payload
|
66
|
+
# {"alg"=>"none"} # header
|
67
|
+
# ]
|
87
68
|
```
|
88
69
|
|
89
70
|
### **HMAC**
|
90
71
|
|
91
|
-
|
92
|
-
|
93
|
-
|
72
|
+
- HS256 - HMAC using SHA-256 hash algorithm
|
73
|
+
- HS384 - HMAC using SHA-384 hash algorithm
|
74
|
+
- HS512 - HMAC using SHA-512 hash algorithm
|
94
75
|
|
95
76
|
```ruby
|
96
|
-
|
77
|
+
payload = { data: 'test' }
|
97
78
|
hmac_secret = 'my$ecretK3y'
|
98
79
|
|
99
|
-
token = JWT.encode
|
100
|
-
|
101
|
-
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
|
102
|
-
puts token
|
103
|
-
|
104
|
-
decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
80
|
+
token = JWT.encode(payload, hmac_secret, 'HS256')
|
81
|
+
# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
|
105
82
|
|
106
|
-
|
107
|
-
# [
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
puts decoded_token
|
83
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
84
|
+
# => [
|
85
|
+
# {"data"=>"test"}, # payload
|
86
|
+
# {"alg"=>"HS256"} # header
|
87
|
+
# ]
|
112
88
|
```
|
113
89
|
|
114
90
|
### **RSA**
|
115
91
|
|
116
|
-
|
117
|
-
|
118
|
-
|
92
|
+
- RS256 - RSA using SHA-256 hash algorithm
|
93
|
+
- RS384 - RSA using SHA-384 hash algorithm
|
94
|
+
- RS512 - RSA using SHA-512 hash algorithm
|
119
95
|
|
120
96
|
```ruby
|
121
|
-
|
122
|
-
|
97
|
+
payload = { data: 'test' }
|
98
|
+
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
99
|
+
rsa_public = rsa_private.public_key
|
123
100
|
|
124
|
-
token = JWT.encode
|
101
|
+
token = JWT.encode(payload, rsa_private, 'RS256')
|
102
|
+
# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
|
125
103
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# Array
|
132
|
-
# [
|
133
|
-
# {"data"=>"test"}, # payload
|
134
|
-
# {"alg"=>"RS256"} # header
|
135
|
-
# ]
|
136
|
-
puts decoded_token
|
104
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
|
105
|
+
# => [
|
106
|
+
# {"data"=>"test"}, # payload
|
107
|
+
# {"alg"=>"RS256"} # header
|
108
|
+
# ]
|
137
109
|
```
|
138
110
|
|
139
111
|
### **ECDSA**
|
140
112
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
113
|
+
- ES256 - ECDSA using P-256 and SHA-256
|
114
|
+
- ES384 - ECDSA using P-384 and SHA-384
|
115
|
+
- ES512 - ECDSA using P-521 and SHA-512
|
116
|
+
- ES256K - ECDSA using P-256K and SHA-256
|
145
117
|
|
146
118
|
```ruby
|
119
|
+
payload = { data: 'test' }
|
147
120
|
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
148
121
|
|
149
|
-
token = JWT.encode
|
122
|
+
token = JWT.encode(payload, ecdsa_key, 'ES256')
|
123
|
+
# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
|
150
124
|
|
151
|
-
|
152
|
-
|
125
|
+
decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
|
126
|
+
# => [
|
127
|
+
# {"test"=>"data"}, # payload
|
128
|
+
# {"alg"=>"ES256"} # header
|
129
|
+
# ]
|
130
|
+
```
|
153
131
|
|
154
|
-
|
132
|
+
### **EdDSA**
|
155
133
|
|
156
|
-
|
157
|
-
# [
|
158
|
-
# {"test"=>"data"}, # payload
|
159
|
-
# {"alg"=>"ES256"} # header
|
160
|
-
# ]
|
161
|
-
puts decoded_token
|
162
|
-
```
|
134
|
+
Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
|
163
135
|
|
164
|
-
### **
|
136
|
+
### **RSASSA-PSS**
|
165
137
|
|
166
|
-
|
138
|
+
- PS256 - RSASSA-PSS using SHA-256 hash algorithm
|
139
|
+
- PS384 - RSASSA-PSS using SHA-384 hash algorithm
|
140
|
+
- PS512 - RSASSA-PSS using SHA-512 hash algorithm
|
167
141
|
|
168
142
|
```ruby
|
169
|
-
|
143
|
+
payload = { data: 'test' }
|
144
|
+
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
145
|
+
rsa_public = rsa_private.public_key
|
146
|
+
|
147
|
+
token = JWT.encode(payload, rsa_private, 'PS256')
|
148
|
+
# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
|
149
|
+
|
150
|
+
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
|
151
|
+
# => [
|
152
|
+
# {"data"=>"test"}, # payload
|
153
|
+
# {"alg"=>"PS256"} # header
|
154
|
+
# ]
|
170
155
|
```
|
171
156
|
|
172
|
-
|
157
|
+
### **Custom algorithms**
|
158
|
+
|
159
|
+
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:
|
160
|
+
|
161
|
+
- For decoding/verifying: The object must implement the methods `alg` and `verify`.
|
162
|
+
- For encoding/signing: The object must implement the methods `alg` and `sign`.
|
173
163
|
|
174
|
-
|
164
|
+
For customization options check the details from `JWT::JWA::SigningAlgorithm`.
|
175
165
|
|
176
166
|
```ruby
|
177
|
-
|
178
|
-
|
179
|
-
|
167
|
+
module CustomHS512Algorithm
|
168
|
+
extend JWT::JWA::SigningAlgorithm
|
169
|
+
|
170
|
+
def self.alg
|
171
|
+
'HS512'
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.sign(data:, signing_key:)
|
175
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
|
176
|
+
end
|
180
177
|
|
181
|
-
|
182
|
-
|
178
|
+
def self.verify(data:, signature:, verification_key:)
|
179
|
+
::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
|
180
|
+
end
|
181
|
+
end
|
183
182
|
|
184
|
-
|
185
|
-
|
186
|
-
#
|
187
|
-
# {"test"=>"data"}, # payload
|
188
|
-
# {"alg"=>"ED25519"} # header
|
189
|
-
# ]
|
183
|
+
payload = { data: 'test' }
|
184
|
+
token = JWT.encode(payload, 'secret', CustomHS512Algorithm)
|
185
|
+
# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
|
190
186
|
|
187
|
+
decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
|
188
|
+
# => [
|
189
|
+
# {"data"=>"test"}, # payload
|
190
|
+
# {"alg"=>"HS512"} # header
|
191
|
+
# ]
|
191
192
|
```
|
192
193
|
|
193
|
-
###
|
194
|
+
### Add custom header fields
|
194
195
|
|
195
|
-
|
196
|
+
The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
|
197
|
+
To add custom header fields you need to pass `header_fields` parameter
|
196
198
|
|
197
199
|
```ruby
|
198
|
-
|
200
|
+
payload = { data: 'test' }
|
201
|
+
|
202
|
+
token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
|
203
|
+
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
|
204
|
+
|
205
|
+
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
|
206
|
+
# => [
|
207
|
+
# {"data"=>"test"}, # payload
|
208
|
+
# {"typ"=>"JWT", "alg"=>"none"} # header
|
209
|
+
# ]
|
199
210
|
```
|
200
211
|
|
201
|
-
|
202
|
-
|
203
|
-
|
212
|
+
## `JWT::Token` and `JWT::EncodedToken`
|
213
|
+
|
214
|
+
The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs.
|
215
|
+
|
216
|
+
### Signing and encoding a token
|
204
217
|
|
205
218
|
```ruby
|
206
|
-
|
207
|
-
|
219
|
+
payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }
|
220
|
+
header = { kid: 'hmac' }
|
221
|
+
|
222
|
+
token = JWT::Token.new(payload: payload, header: header)
|
223
|
+
token.sign!(algorithm: 'HS256', key: "secret")
|
224
|
+
|
225
|
+
token.jwt
|
226
|
+
# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
|
227
|
+
```
|
208
228
|
|
209
|
-
|
229
|
+
### Verifying and decoding a token
|
210
230
|
|
211
|
-
|
212
|
-
puts token
|
231
|
+
The `JWT::EncodedToken` can be used as a token object that allows verification of signatures and claims.
|
213
232
|
|
214
|
-
|
233
|
+
```ruby
|
234
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
215
235
|
|
216
|
-
|
217
|
-
#
|
218
|
-
|
219
|
-
|
220
|
-
# ]
|
221
|
-
|
236
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
|
237
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
|
238
|
+
encoded_token.verify_claims!(:exp, :jti)
|
239
|
+
encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
|
240
|
+
encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
|
241
|
+
encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
|
242
|
+
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
|
222
243
|
```
|
223
244
|
|
224
|
-
|
245
|
+
The `JWT::EncodedToken#verify!` method can be used to verify signature and claim verification in one go. The `exp` claim is verified by default.
|
225
246
|
|
226
|
-
|
247
|
+
```ruby
|
248
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
249
|
+
encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
|
250
|
+
encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
|
251
|
+
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
|
252
|
+
```
|
253
|
+
|
254
|
+
A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key.
|
227
255
|
|
228
256
|
```ruby
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
257
|
+
jwk_json = '{
|
258
|
+
"kty": "oct",
|
259
|
+
"k": "c2VjcmV0",
|
260
|
+
"alg": "HS256",
|
261
|
+
"kid": "hmac"
|
262
|
+
}'
|
233
263
|
|
234
|
-
|
235
|
-
alg_to_validate == alg
|
236
|
-
end
|
264
|
+
jwk = JWT::JWK.import(JSON.parse(jwk_json))
|
237
265
|
|
238
|
-
|
239
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key)
|
240
|
-
end
|
266
|
+
token = JWT::Token.new(payload: payload, header: header)
|
241
267
|
|
242
|
-
|
243
|
-
::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
|
244
|
-
end
|
245
|
-
end
|
268
|
+
token.sign!(key: jwk)
|
246
269
|
|
247
|
-
|
248
|
-
|
270
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
271
|
+
encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk})
|
249
272
|
```
|
250
273
|
|
251
|
-
|
252
|
-
JSON Web Token defines some reserved claim names and defines how they should be
|
253
|
-
used. JWT supports these reserved claim names:
|
274
|
+
#### Using a keyfinder
|
254
275
|
|
255
|
-
|
256
|
-
- 'nbf' (Not Before Time) Claim
|
257
|
-
- 'iss' (Issuer) Claim
|
258
|
-
- 'aud' (Audience) Claim
|
259
|
-
- 'jti' (JWT ID) Claim
|
260
|
-
- 'iat' (Issued At) Claim
|
261
|
-
- 'sub' (Subject) Claim
|
276
|
+
A keyfinder can be used to verify a signature. A keyfinder is an object responding to the `#call` method. The method expects to receive one argument, which is the token to be verified.
|
262
277
|
|
263
|
-
|
264
|
-
Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
|
265
|
-
To add custom header fields you need to pass `header_fields` parameter
|
278
|
+
An example on using the built-in JWK keyfinder.
|
266
279
|
|
267
280
|
```ruby
|
268
|
-
|
281
|
+
# Create and sign a token
|
282
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
|
283
|
+
token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
|
284
|
+
token.sign!(algorithm: 'RS256', key: jwk.signing_key)
|
285
|
+
|
286
|
+
# Create keyfinder object, verify and decode token
|
287
|
+
key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
|
288
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
289
|
+
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
|
290
|
+
encoded_token.payload # => { 'pay' => 'load' }
|
269
291
|
```
|
270
292
|
|
271
|
-
|
293
|
+
Using a custom keyfinder proc.
|
272
294
|
|
273
295
|
```ruby
|
274
|
-
|
296
|
+
# Create and sign a token
|
297
|
+
key = OpenSSL::PKey::RSA.generate(2048)
|
298
|
+
token = JWT::Token.new(payload: { pay: 'load' })
|
299
|
+
token.sign!(algorithm: 'RS256', key: key)
|
275
300
|
|
276
|
-
|
301
|
+
# Verify and decode token
|
302
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
303
|
+
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
|
304
|
+
encoded_token.payload # => { 'pay' => 'load' }
|
305
|
+
```
|
277
306
|
|
278
|
-
|
279
|
-
token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
|
307
|
+
### Detached payload
|
280
308
|
|
281
|
-
#
|
282
|
-
puts token
|
309
|
+
The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
|
283
310
|
|
284
|
-
|
285
|
-
|
311
|
+
```ruby
|
312
|
+
token = JWT::Token.new(payload: { pay: 'load' })
|
313
|
+
token.sign!(algorithm: 'HS256', key: "secret")
|
314
|
+
token.detach_payload!
|
315
|
+
token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
|
316
|
+
token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
|
317
|
+
```
|
318
|
+
|
319
|
+
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.
|
286
320
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
#
|
292
|
-
puts decoded_token
|
321
|
+
```ruby
|
322
|
+
encoded_token = JWT::EncodedToken.new(token.jwt)
|
323
|
+
encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
|
324
|
+
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
|
325
|
+
encoded_token.payload # => {"pay"=>"load"}
|
293
326
|
```
|
294
327
|
|
328
|
+
## Claims
|
329
|
+
|
330
|
+
JSON Web Token defines some reserved claim names and defines how they should be
|
331
|
+
used. JWT supports these reserved claim names:
|
332
|
+
|
333
|
+
- 'exp' (Expiration Time) Claim
|
334
|
+
- 'nbf' (Not Before Time) Claim
|
335
|
+
- 'iss' (Issuer) Claim
|
336
|
+
- 'aud' (Audience) Claim
|
337
|
+
- 'jti' (JWT ID) Claim
|
338
|
+
- 'iat' (Issued At) Claim
|
339
|
+
- 'sub' (Subject) Claim
|
340
|
+
|
295
341
|
### Expiration Time Claim
|
296
342
|
|
297
343
|
From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
|
298
344
|
|
299
|
-
> The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a
|
300
|
-
|
301
|
-
**Handle Expiration Claim**
|
345
|
+
> The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
|
302
346
|
|
303
347
|
```ruby
|
304
348
|
exp = Time.now.to_i + 4 * 3600
|
305
349
|
exp_payload = { data: 'data', exp: exp }
|
306
350
|
|
307
|
-
token = JWT.encode
|
351
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
308
352
|
|
309
353
|
begin
|
310
|
-
decoded_token = JWT.decode
|
354
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
311
355
|
rescue JWT::ExpiredSignature
|
312
356
|
# Handle expired token, e.g. logout user or deny access
|
313
357
|
end
|
314
358
|
```
|
315
359
|
|
316
360
|
The Expiration Claim verification can be disabled.
|
361
|
+
|
317
362
|
```ruby
|
318
363
|
# Decode token without raising JWT::ExpiredSignature error
|
319
|
-
JWT.decode
|
364
|
+
JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
|
320
365
|
```
|
321
366
|
|
322
|
-
|
367
|
+
Leeway and the exp claim.
|
323
368
|
|
324
369
|
```ruby
|
325
370
|
exp = Time.now.to_i - 10
|
@@ -328,11 +373,11 @@ leeway = 30 # seconds
|
|
328
373
|
exp_payload = { data: 'data', exp: exp }
|
329
374
|
|
330
375
|
# build expired token
|
331
|
-
token = JWT.encode
|
376
|
+
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
|
332
377
|
|
333
378
|
begin
|
334
379
|
# add leeway to ensure the token is still accepted
|
335
|
-
decoded_token = JWT.decode
|
380
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
|
336
381
|
rescue JWT::ExpiredSignature
|
337
382
|
# Handle expired token, e.g. logout user or deny access
|
338
383
|
end
|
@@ -342,30 +387,29 @@ end
|
|
342
387
|
|
343
388
|
From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5):
|
344
389
|
|
345
|
-
> The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a
|
346
|
-
|
347
|
-
**Handle Not Before Claim**
|
390
|
+
> The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
|
348
391
|
|
349
392
|
```ruby
|
350
393
|
nbf = Time.now.to_i - 3600
|
351
394
|
nbf_payload = { data: 'data', nbf: nbf }
|
352
395
|
|
353
|
-
token = JWT.encode
|
396
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
354
397
|
|
355
398
|
begin
|
356
|
-
decoded_token = JWT.decode
|
399
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
|
357
400
|
rescue JWT::ImmatureSignature
|
358
401
|
# Handle invalid token, e.g. logout user or deny access
|
359
402
|
end
|
360
403
|
```
|
361
404
|
|
362
405
|
The Not Before Claim verification can be disabled.
|
406
|
+
|
363
407
|
```ruby
|
364
408
|
# Decode token without raising JWT::ImmatureSignature error
|
365
|
-
JWT.decode
|
409
|
+
JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
|
366
410
|
```
|
367
411
|
|
368
|
-
|
412
|
+
Leeway and the nbf claim.
|
369
413
|
|
370
414
|
```ruby
|
371
415
|
nbf = Time.now.to_i + 10
|
@@ -374,11 +418,11 @@ leeway = 30
|
|
374
418
|
nbf_payload = { data: 'data', nbf: nbf }
|
375
419
|
|
376
420
|
# build expired token
|
377
|
-
token = JWT.encode
|
421
|
+
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
|
378
422
|
|
379
423
|
begin
|
380
424
|
# add leeway to ensure the token is valid
|
381
|
-
decoded_token = JWT.decode
|
425
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
|
382
426
|
rescue JWT::ImmatureSignature
|
383
427
|
# Handle invalid token, e.g. logout user or deny access
|
384
428
|
end
|
@@ -388,7 +432,7 @@ end
|
|
388
432
|
|
389
433
|
From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1):
|
390
434
|
|
391
|
-
> The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a
|
435
|
+
> The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL.
|
392
436
|
|
393
437
|
You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
|
394
438
|
|
@@ -396,11 +440,11 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
|
|
396
440
|
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
|
397
441
|
iss_payload = { data: 'data', iss: iss }
|
398
442
|
|
399
|
-
token = JWT.encode
|
443
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
400
444
|
|
401
445
|
begin
|
402
446
|
# Add iss to the validation to check if the token has been manipulated
|
403
|
-
decoded_token = JWT.decode
|
447
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
|
404
448
|
rescue JWT::InvalidIssuerError
|
405
449
|
# Handle invalid token, e.g. logout user or deny access
|
406
450
|
end
|
@@ -411,24 +455,24 @@ On supported ruby versions (>= 2.5) you can also delegate to methods, on older v
|
|
411
455
|
to convert them to proc (using `to_proc`)
|
412
456
|
|
413
457
|
```ruby
|
414
|
-
JWT.decode
|
458
|
+
JWT.decode(token, hmac_secret, true,
|
415
459
|
iss: %r'https://my.awesome.website/',
|
416
460
|
verify_iss: true,
|
417
|
-
algorithm: 'HS256'
|
461
|
+
algorithm: 'HS256')
|
418
462
|
```
|
419
463
|
|
420
464
|
```ruby
|
421
|
-
JWT.decode
|
465
|
+
JWT.decode(token, hmac_secret, true,
|
422
466
|
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
423
467
|
verify_iss: true,
|
424
|
-
algorithm: 'HS256'
|
468
|
+
algorithm: 'HS256')
|
425
469
|
```
|
426
470
|
|
427
471
|
```ruby
|
428
|
-
JWT.decode
|
472
|
+
JWT.decode(token, hmac_secret, true,
|
429
473
|
iss: method(:valid_issuer?),
|
430
474
|
verify_iss: true,
|
431
|
-
algorithm: 'HS256'
|
475
|
+
algorithm: 'HS256')
|
432
476
|
|
433
477
|
# somewhere in the same class:
|
434
478
|
def valid_issuer?(issuer)
|
@@ -440,17 +484,17 @@ end
|
|
440
484
|
|
441
485
|
From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
|
442
486
|
|
443
|
-
> The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a
|
487
|
+
> The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a **_StringOrURI_** value. In the special case when the JWT has one audience, the `aud` value MAY be a single case-sensitive string containing a **_StringOrURI_** value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
|
444
488
|
|
445
489
|
```ruby
|
446
490
|
aud = ['Young', 'Old']
|
447
491
|
aud_payload = { data: 'data', aud: aud }
|
448
492
|
|
449
|
-
token = JWT.encode
|
493
|
+
token = JWT.encode(aud_payload, hmac_secret, 'HS256')
|
450
494
|
|
451
495
|
begin
|
452
496
|
# Add aud to the validation to check if the token has been manipulated
|
453
|
-
decoded_token = JWT.decode
|
497
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
|
454
498
|
rescue JWT::InvalidAudError
|
455
499
|
# Handle invalid token, e.g. logout user or deny access
|
456
500
|
puts 'Audience Error'
|
@@ -469,15 +513,15 @@ jti_raw = [hmac_secret, iat].join(':').to_s
|
|
469
513
|
jti = Digest::MD5.hexdigest(jti_raw)
|
470
514
|
jti_payload = { data: 'data', iat: iat, jti: jti }
|
471
515
|
|
472
|
-
token = JWT.encode
|
516
|
+
token = JWT.encode(jti_payload, hmac_secret, 'HS256')
|
473
517
|
|
474
518
|
begin
|
475
519
|
# If :verify_jti is true, validation will pass if a JTI is present
|
476
|
-
#decoded_token = JWT.decode
|
520
|
+
#decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
|
477
521
|
# Alternatively, pass a proc with your own code to check if the JTI has already been used
|
478
|
-
decoded_token = JWT.decode
|
522
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
|
479
523
|
# or
|
480
|
-
decoded_token = JWT.decode
|
524
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
|
481
525
|
rescue JWT::InvalidJtiError
|
482
526
|
# Handle invalid token, e.g. logout user or deny access
|
483
527
|
puts 'Error'
|
@@ -488,19 +532,17 @@ end
|
|
488
532
|
|
489
533
|
From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
|
490
534
|
|
491
|
-
> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a
|
492
|
-
|
493
|
-
**Handle Issued At Claim**
|
535
|
+
> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
|
494
536
|
|
495
537
|
```ruby
|
496
538
|
iat = Time.now.to_i
|
497
539
|
iat_payload = { data: 'data', iat: iat }
|
498
540
|
|
499
|
-
token = JWT.encode
|
541
|
+
token = JWT.encode(iat_payload, hmac_secret, 'HS256')
|
500
542
|
|
501
543
|
begin
|
502
544
|
# Add iat to the validation to check if the token has been manipulated
|
503
|
-
decoded_token = JWT.decode
|
545
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
|
504
546
|
rescue JWT::InvalidIatError
|
505
547
|
# Handle invalid token, e.g. logout user or deny access
|
506
548
|
end
|
@@ -510,22 +552,39 @@ end
|
|
510
552
|
|
511
553
|
From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
|
512
554
|
|
513
|
-
> The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a
|
555
|
+
> The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL.
|
514
556
|
|
515
557
|
```ruby
|
516
558
|
sub = 'Subject'
|
517
559
|
sub_payload = { data: 'data', sub: sub }
|
518
560
|
|
519
|
-
token = JWT.encode
|
561
|
+
token = JWT.encode(sub_payload, hmac_secret, 'HS256')
|
520
562
|
|
521
563
|
begin
|
522
564
|
# Add sub to the validation to check if the token has been manipulated
|
523
|
-
decoded_token = JWT.decode
|
565
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
|
524
566
|
rescue JWT::InvalidSubError
|
525
567
|
# Handle invalid token, e.g. logout user or deny access
|
526
568
|
end
|
527
569
|
```
|
528
570
|
|
571
|
+
### Standalone claim verification
|
572
|
+
|
573
|
+
The JWT claim verifications can be used to verify any Hash to include expected keys and values.
|
574
|
+
|
575
|
+
A few example on verifying the claims for a payload:
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
|
579
|
+
JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
|
580
|
+
# => true
|
581
|
+
JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp)
|
582
|
+
# => [#<struct JWT::Claims::Error message="Signature has expired">]
|
583
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
|
584
|
+
|
585
|
+
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
|
586
|
+
```
|
587
|
+
|
529
588
|
### Finding a Key
|
530
589
|
|
531
590
|
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.
|
@@ -536,7 +595,7 @@ iss_payload = { data: 'data', iss: issuers.first }
|
|
536
595
|
|
537
596
|
secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
|
538
597
|
|
539
|
-
token = JWT.encode
|
598
|
+
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
|
540
599
|
|
541
600
|
begin
|
542
601
|
# Add iss to the validation to check if the token has been manipulated
|
@@ -551,9 +610,10 @@ end
|
|
551
610
|
### Required Claims
|
552
611
|
|
553
612
|
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
613
|
+
|
554
614
|
```ruby
|
555
615
|
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
556
|
-
JWT.decode
|
616
|
+
JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
|
557
617
|
```
|
558
618
|
|
559
619
|
### X.509 certificates in x5c header
|
@@ -577,7 +637,7 @@ rescue JWT::DecodeError
|
|
577
637
|
end
|
578
638
|
```
|
579
639
|
|
580
|
-
|
640
|
+
## JSON Web Key (JWK)
|
581
641
|
|
582
642
|
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.
|
583
643
|
|
@@ -604,14 +664,14 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
|
|
604
664
|
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
|
605
665
|
```
|
606
666
|
|
607
|
-
|
608
|
-
The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
|
667
|
+
The `jwks` option can also be given as a lambda that evaluates every time a key identifier is resolved.
|
609
668
|
This can be used to implement caching of remotely fetched JWK Sets.
|
610
669
|
|
611
|
-
|
670
|
+
Key identifiers can be specified using `kid`, `x5t` header parameters.
|
671
|
+
If the requested identifier is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
|
612
672
|
The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
|
613
673
|
|
614
|
-
Tokens without a specified `kid` are rejected by default.
|
674
|
+
Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default.
|
615
675
|
This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
|
616
676
|
|
617
677
|
```ruby
|
@@ -690,22 +750,27 @@ jwk_hash = jwk.export
|
|
690
750
|
thumbprint_as_the_kid = jwk_hash[:kid]
|
691
751
|
```
|
692
752
|
|
693
|
-
|
753
|
+
## Development and testing
|
694
754
|
|
695
|
-
|
755
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
696
756
|
|
697
757
|
```bash
|
698
|
-
|
758
|
+
bundle install
|
759
|
+
bundle exec appraisal rake test
|
699
760
|
```
|
700
761
|
|
701
|
-
|
762
|
+
## Releasing
|
763
|
+
|
764
|
+
To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:
|
702
765
|
|
703
766
|
```bash
|
704
|
-
|
705
|
-
bundle exec appraisal rake test
|
767
|
+
rake release:source_control_push
|
706
768
|
```
|
707
769
|
|
770
|
+
This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org.
|
771
|
+
|
708
772
|
## How to contribute
|
773
|
+
|
709
774
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
710
775
|
|
711
776
|
## Contributors
|