jwt 2.1.0 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +74 -0
- data/.gitignore +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +15 -16
- data/.rubocop_todo.yml +191 -0
- data/{.ebert.yml → .sourcelevel.yml} +1 -1
- data/AUTHORS +101 -0
- data/Appraisals +10 -0
- data/CHANGELOG.md +247 -19
- data/Gemfile +2 -0
- data/README.md +154 -89
- data/Rakefile +4 -1
- data/lib/jwt.rb +9 -42
- data/lib/jwt/algos.rb +44 -0
- data/lib/jwt/algos/ecdsa.rb +1 -1
- data/lib/jwt/algos/hmac.rb +1 -0
- data/lib/jwt/algos/none.rb +15 -0
- data/lib/jwt/algos/ps.rb +43 -0
- data/lib/jwt/algos/unsupported.rb +5 -4
- data/lib/jwt/base64.rb +19 -0
- data/lib/jwt/claims_validator.rb +35 -0
- data/lib/jwt/decode.rb +85 -25
- data/lib/jwt/encode.rb +43 -25
- data/lib/jwt/error.rb +4 -0
- data/lib/jwt/json.rb +18 -0
- data/lib/jwt/jwk.rb +51 -0
- data/lib/jwt/jwk/ec.rb +150 -0
- data/lib/jwt/jwk/hmac.rb +58 -0
- data/lib/jwt/jwk/key_base.rb +18 -0
- data/lib/jwt/jwk/key_finder.rb +62 -0
- data/lib/jwt/jwk/rsa.rb +115 -0
- data/lib/jwt/security_utils.rb +6 -0
- data/lib/jwt/signature.rb +9 -20
- data/lib/jwt/verify.rb +1 -5
- data/lib/jwt/version.rb +2 -2
- data/ruby-jwt.gemspec +4 -7
- metadata +30 -109
- data/.codeclimate.yml +0 -20
- data/.reek.yml +0 -40
- data/.travis.yml +0 -14
- data/Manifest +0 -8
- data/spec/fixtures/certs/ec256-private.pem +0 -8
- data/spec/fixtures/certs/ec256-public.pem +0 -4
- data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
- data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
- data/spec/fixtures/certs/ec384-private.pem +0 -9
- data/spec/fixtures/certs/ec384-public.pem +0 -5
- data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
- data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
- data/spec/fixtures/certs/ec512-private.pem +0 -10
- data/spec/fixtures/certs/ec512-public.pem +0 -6
- data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
- data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
- data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
- data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
- data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
- data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
- data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
- data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
- data/spec/integration/readme_examples_spec.rb +0 -202
- data/spec/jwt/verify_spec.rb +0 -232
- data/spec/jwt_spec.rb +0 -315
- data/spec/spec_helper.rb +0 -28
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
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://
|
4
|
+
[![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=master)](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)
|
8
|
+
[![SourceLevel](https://app.sourcelevel.io/github/jwt/-/ruby-jwt.svg)](https://app.sourcelevel.io/github/jwt/-/ruby-jwt)
|
8
9
|
|
9
|
-
A
|
10
|
+
A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
|
10
11
|
|
11
12
|
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
13
|
|
@@ -15,11 +16,17 @@ If you have further questions related to development or usage, join us: [ruby-jw
|
|
15
16
|
* Ruby 1.9.3 support was dropped at December 31st, 2016.
|
16
17
|
* 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
|
|
19
|
+
## Sponsors
|
20
|
+
|
21
|
+
|Logo|Message|
|
22
|
+
|-|-|
|
23
|
+
|![auth0 logo](https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png)|If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth)|
|
24
|
+
|
18
25
|
## Installing
|
19
26
|
|
20
27
|
### Using Rubygems:
|
21
28
|
```bash
|
22
|
-
|
29
|
+
gem install jwt
|
23
30
|
```
|
24
31
|
|
25
32
|
### Using Bundler:
|
@@ -31,7 +38,7 @@ And run `bundle install`
|
|
31
38
|
|
32
39
|
## Algorithms and Usage
|
33
40
|
|
34
|
-
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/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/).
|
41
|
+
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/2015/03/31/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**
|
35
42
|
|
36
43
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
37
44
|
|
@@ -42,12 +49,12 @@ See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values
|
|
42
49
|
```ruby
|
43
50
|
require 'jwt'
|
44
51
|
|
45
|
-
payload = {:
|
52
|
+
payload = { data: 'test' }
|
46
53
|
|
47
54
|
# IMPORTANT: set nil as password parameter
|
48
55
|
token = JWT.encode payload, nil, 'none'
|
49
56
|
|
50
|
-
#
|
57
|
+
# eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
|
51
58
|
puts token
|
52
59
|
|
53
60
|
# Set password to nil and validation to false otherwise this won't work
|
@@ -73,10 +80,25 @@ hmac_secret = 'my$ecretK3y'
|
|
73
80
|
|
74
81
|
token = JWT.encode payload, hmac_secret, 'HS256'
|
75
82
|
|
76
|
-
#
|
83
|
+
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y
|
84
|
+
puts token
|
85
|
+
|
86
|
+
decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
87
|
+
|
88
|
+
# Array
|
89
|
+
# [
|
90
|
+
# {"data"=>"test"}, # payload
|
91
|
+
# {"alg"=>"HS256"} # header
|
92
|
+
# ]
|
93
|
+
puts decoded_token
|
94
|
+
|
95
|
+
# Without secret key
|
96
|
+
token = JWT.encode payload, nil, 'HS256'
|
97
|
+
|
98
|
+
# eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pVzcY2dX8JNM3LzIYeP2B1e1Wcpt1K3TWVvIYSF4x-o
|
77
99
|
puts token
|
78
100
|
|
79
|
-
decoded_token = JWT.decode token,
|
101
|
+
decoded_token = JWT.decode token, nil, true, { algorithm: 'HS256' }
|
80
102
|
|
81
103
|
# Array
|
82
104
|
# [
|
@@ -104,10 +126,10 @@ rsa_public = rsa_private.public_key
|
|
104
126
|
|
105
127
|
token = JWT.encode payload, rsa_private, 'RS256'
|
106
128
|
|
107
|
-
#
|
129
|
+
# eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
|
108
130
|
puts token
|
109
131
|
|
110
|
-
decoded_token = JWT.decode token, rsa_public, true, { :
|
132
|
+
decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
|
111
133
|
|
112
134
|
# Array
|
113
135
|
# [
|
@@ -131,10 +153,10 @@ ecdsa_public.private_key = nil
|
|
131
153
|
|
132
154
|
token = JWT.encode payload, ecdsa_key, 'ES256'
|
133
155
|
|
134
|
-
#
|
156
|
+
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
135
157
|
puts token
|
136
158
|
|
137
|
-
decoded_token = JWT.decode token, ecdsa_public, true, { :
|
159
|
+
decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
|
138
160
|
|
139
161
|
# Array
|
140
162
|
# [
|
@@ -154,17 +176,17 @@ gem 'rbnacl'
|
|
154
176
|
|
155
177
|
For more detailed installation instruction check the official [repository](https://github.com/cryptosphere/rbnacl) on GitHub.
|
156
178
|
|
157
|
-
* ED25519
|
179
|
+
* ED25519
|
158
180
|
|
159
|
-
```ruby
|
160
|
-
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(
|
181
|
+
```ruby
|
182
|
+
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF')
|
161
183
|
public_key = private_key.verify_key
|
162
|
-
token = JWT.encode payload, private_key, 'ED25519'
|
184
|
+
token = JWT.encode payload, private_key, 'ED25519'
|
163
185
|
|
164
|
-
# eyJhbGciOiJFRDI1NTE5In0.
|
186
|
+
# eyJhbGciOiJFRDI1NTE5In0.eyJkYXRhIjoidGVzdCJ9.6xIztXyOupskddGA_RvKU76V9b2dCQUYhoZEVFnRimJoPYIzZ2Fm47CWw8k2NTCNpgfAuxg9OXjaiVK7MvrbCQ
|
165
187
|
puts token
|
166
188
|
|
167
|
-
decoded_token = JWT.decode token, public_key, true, {:
|
189
|
+
decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
168
190
|
# Array
|
169
191
|
# [
|
170
192
|
# {"test"=>"data"}, # payload
|
@@ -175,7 +197,34 @@ decoded_token = JWT.decode token, public_key, true, {:algorithm => 'ED25519' }
|
|
175
197
|
|
176
198
|
**RSASSA-PSS**
|
177
199
|
|
178
|
-
|
200
|
+
In order to use this algorithm you need to add the `openssl` gem to you `Gemfile` with a version greater or equal to `2.1`.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
gem 'openssl', '~> 2.1'
|
204
|
+
```
|
205
|
+
|
206
|
+
* PS256 - RSASSA-PSS using SHA-256 hash algorithm
|
207
|
+
* PS384 - RSASSA-PSS using SHA-384 hash algorithm
|
208
|
+
* PS512 - RSASSA-PSS using SHA-512 hash algorithm
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
212
|
+
rsa_public = rsa_private.public_key
|
213
|
+
|
214
|
+
token = JWT.encode payload, rsa_private, 'PS256'
|
215
|
+
|
216
|
+
# eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
|
217
|
+
puts token
|
218
|
+
|
219
|
+
decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
|
220
|
+
|
221
|
+
# Array
|
222
|
+
# [
|
223
|
+
# {"data"=>"test"}, # payload
|
224
|
+
# {"alg"=>"PS256"} # header
|
225
|
+
# ]
|
226
|
+
puts decoded_token
|
227
|
+
```
|
179
228
|
|
180
229
|
## Support for reserved claim names
|
181
230
|
JSON Web Token defines some reserved claim names and defines how they should be
|
@@ -190,7 +239,7 @@ used. JWT supports these reserved claim names:
|
|
190
239
|
- 'sub' (Subject) Claim
|
191
240
|
|
192
241
|
## Add custom header fields
|
193
|
-
Ruby-jwt gem supports custom [header fields]
|
242
|
+
Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
|
194
243
|
To add custom header fields you need to pass `header_fields` parameter
|
195
244
|
|
196
245
|
```ruby
|
@@ -202,12 +251,12 @@ token = JWT.encode payload, key, algorithm='HS256', header_fields={}
|
|
202
251
|
```ruby
|
203
252
|
require 'jwt'
|
204
253
|
|
205
|
-
payload = {:
|
254
|
+
payload = { data: 'test' }
|
206
255
|
|
207
256
|
# IMPORTANT: set nil as password parameter
|
208
|
-
token = JWT.encode payload, nil, 'none', { :
|
257
|
+
token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
|
209
258
|
|
210
|
-
#
|
259
|
+
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
|
211
260
|
puts token
|
212
261
|
|
213
262
|
# Set password to nil and validation to false otherwise this won't work
|
@@ -231,31 +280,37 @@ From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.i
|
|
231
280
|
|
232
281
|
```ruby
|
233
282
|
exp = Time.now.to_i + 4 * 3600
|
234
|
-
exp_payload = { :
|
283
|
+
exp_payload = { data: 'data', exp: exp }
|
235
284
|
|
236
285
|
token = JWT.encode exp_payload, hmac_secret, 'HS256'
|
237
286
|
|
238
287
|
begin
|
239
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
288
|
+
decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
240
289
|
rescue JWT::ExpiredSignature
|
241
290
|
# Handle expired token, e.g. logout user or deny access
|
242
291
|
end
|
243
292
|
```
|
244
293
|
|
294
|
+
The Expiration Claim verification can be disabled.
|
295
|
+
```ruby
|
296
|
+
# Decode token without raising JWT::ExpiredSignature error
|
297
|
+
JWT.decode token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }
|
298
|
+
```
|
299
|
+
|
245
300
|
**Adding Leeway**
|
246
301
|
|
247
302
|
```ruby
|
248
303
|
exp = Time.now.to_i - 10
|
249
304
|
leeway = 30 # seconds
|
250
305
|
|
251
|
-
exp_payload = { :
|
306
|
+
exp_payload = { data: 'data', exp: exp }
|
252
307
|
|
253
308
|
# build expired token
|
254
309
|
token = JWT.encode exp_payload, hmac_secret, 'HS256'
|
255
310
|
|
256
311
|
begin
|
257
312
|
# add leeway to ensure the token is still accepted
|
258
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
313
|
+
decoded_token = JWT.decode token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }
|
259
314
|
rescue JWT::ExpiredSignature
|
260
315
|
# Handle expired token, e.g. logout user or deny access
|
261
316
|
end
|
@@ -271,31 +326,37 @@ From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.o
|
|
271
326
|
|
272
327
|
```ruby
|
273
328
|
nbf = Time.now.to_i - 3600
|
274
|
-
nbf_payload = { :
|
329
|
+
nbf_payload = { data: 'data', nbf: nbf }
|
275
330
|
|
276
331
|
token = JWT.encode nbf_payload, hmac_secret, 'HS256'
|
277
332
|
|
278
333
|
begin
|
279
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
334
|
+
decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
280
335
|
rescue JWT::ImmatureSignature
|
281
336
|
# Handle invalid token, e.g. logout user or deny access
|
282
337
|
end
|
283
338
|
```
|
284
339
|
|
340
|
+
The Not Before Claim verification can be disabled.
|
341
|
+
```ruby
|
342
|
+
# Decode token without raising JWT::ImmatureSignature error
|
343
|
+
JWT.decode token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }
|
344
|
+
```
|
345
|
+
|
285
346
|
**Adding Leeway**
|
286
347
|
|
287
348
|
```ruby
|
288
349
|
nbf = Time.now.to_i + 10
|
289
350
|
leeway = 30
|
290
351
|
|
291
|
-
nbf_payload = { :
|
352
|
+
nbf_payload = { data: 'data', nbf: nbf }
|
292
353
|
|
293
354
|
# build expired token
|
294
355
|
token = JWT.encode nbf_payload, hmac_secret, 'HS256'
|
295
356
|
|
296
357
|
begin
|
297
358
|
# add leeway to ensure the token is valid
|
298
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
359
|
+
decoded_token = JWT.decode token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }
|
299
360
|
rescue JWT::ImmatureSignature
|
300
361
|
# Handle invalid token, e.g. logout user or deny access
|
301
362
|
end
|
@@ -311,13 +372,13 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
|
|
311
372
|
|
312
373
|
```ruby
|
313
374
|
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
|
314
|
-
iss_payload = { :
|
375
|
+
iss_payload = { data: 'data', iss: iss }
|
315
376
|
|
316
377
|
token = JWT.encode iss_payload, hmac_secret, 'HS256'
|
317
378
|
|
318
379
|
begin
|
319
380
|
# Add iss to the validation to check if the token has been manipulated
|
320
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
381
|
+
decoded_token = JWT.decode token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }
|
321
382
|
rescue JWT::InvalidIssuerError
|
322
383
|
# Handle invalid token, e.g. logout user or deny access
|
323
384
|
end
|
@@ -331,13 +392,13 @@ From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org
|
|
331
392
|
|
332
393
|
```ruby
|
333
394
|
aud = ['Young', 'Old']
|
334
|
-
aud_payload = { :
|
395
|
+
aud_payload = { data: 'data', aud: aud }
|
335
396
|
|
336
397
|
token = JWT.encode aud_payload, hmac_secret, 'HS256'
|
337
398
|
|
338
399
|
begin
|
339
400
|
# Add aud to the validation to check if the token has been manipulated
|
340
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
401
|
+
decoded_token = JWT.decode token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }
|
341
402
|
rescue JWT::InvalidAudError
|
342
403
|
# Handle invalid token, e.g. logout user or deny access
|
343
404
|
puts 'Audience Error'
|
@@ -354,58 +415,40 @@ From [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/h
|
|
354
415
|
# Use the secret and iat to create a unique key per request to prevent replay attacks
|
355
416
|
jti_raw = [hmac_secret, iat].join(':').to_s
|
356
417
|
jti = Digest::MD5.hexdigest(jti_raw)
|
357
|
-
jti_payload = { :
|
418
|
+
jti_payload = { data: 'data', iat: iat, jti: jti }
|
358
419
|
|
359
420
|
token = JWT.encode jti_payload, hmac_secret, 'HS256'
|
360
421
|
|
361
422
|
begin
|
362
423
|
# If :verify_jti is true, validation will pass if a JTI is present
|
363
|
-
#decoded_token = JWT.decode token, hmac_secret, true, { :
|
424
|
+
#decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }
|
364
425
|
# Alternatively, pass a proc with your own code to check if the JTI has already been used
|
365
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
426
|
+
decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }
|
427
|
+
# or
|
428
|
+
decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }
|
366
429
|
rescue JWT::InvalidJtiError
|
367
430
|
# Handle invalid token, e.g. logout user or deny access
|
368
431
|
puts 'Error'
|
369
432
|
end
|
370
|
-
|
371
433
|
```
|
372
434
|
|
373
435
|
### Issued At Claim
|
374
436
|
|
375
437
|
From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
|
376
438
|
|
377
|
-
> 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. Its value MUST be a number containing a ***NumericDate*** value. Use of this claim is OPTIONAL.
|
439
|
+
> 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.
|
378
440
|
|
379
441
|
**Handle Issued At Claim**
|
380
442
|
|
381
443
|
```ruby
|
382
444
|
iat = Time.now.to_i
|
383
|
-
iat_payload = { :
|
445
|
+
iat_payload = { data: 'data', iat: iat }
|
384
446
|
|
385
447
|
token = JWT.encode iat_payload, hmac_secret, 'HS256'
|
386
448
|
|
387
449
|
begin
|
388
450
|
# Add iat to the validation to check if the token has been manipulated
|
389
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
390
|
-
rescue JWT::InvalidIatError
|
391
|
-
# Handle invalid token, e.g. logout user or deny access
|
392
|
-
end
|
393
|
-
```
|
394
|
-
|
395
|
-
**Adding Leeway**
|
396
|
-
|
397
|
-
```ruby
|
398
|
-
iat = Time.now.to_i + 10
|
399
|
-
leeway = 30 # seconds
|
400
|
-
|
401
|
-
iat_payload = { :data => 'data', :iat => iat }
|
402
|
-
|
403
|
-
# build token issued in the future
|
404
|
-
token = JWT.encode iat_payload, hmac_secret, 'HS256'
|
405
|
-
|
406
|
-
begin
|
407
|
-
# add leeway to ensure the token is accepted
|
408
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :iat_leeway => leeway, :verify_iat => true, :algorithm => 'HS256' }
|
451
|
+
decoded_token = JWT.decode token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }
|
409
452
|
rescue JWT::InvalidIatError
|
410
453
|
# Handle invalid token, e.g. logout user or deny access
|
411
454
|
end
|
@@ -419,18 +462,61 @@ From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/
|
|
419
462
|
|
420
463
|
```ruby
|
421
464
|
sub = 'Subject'
|
422
|
-
sub_payload = { :
|
465
|
+
sub_payload = { data: 'data', sub: sub }
|
423
466
|
|
424
467
|
token = JWT.encode sub_payload, hmac_secret, 'HS256'
|
425
468
|
|
426
469
|
begin
|
427
470
|
# Add sub to the validation to check if the token has been manipulated
|
428
|
-
decoded_token = JWT.decode token, hmac_secret, true, {
|
471
|
+
decoded_token = JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }
|
429
472
|
rescue JWT::InvalidSubError
|
430
473
|
# Handle invalid token, e.g. logout user or deny access
|
431
474
|
end
|
432
475
|
```
|
433
476
|
|
477
|
+
### JSON Web Key (JWK)
|
478
|
+
|
479
|
+
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA public keys.
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
|
483
|
+
payload, headers = { data: 'data' }, { kid: jwk.kid }
|
484
|
+
|
485
|
+
token = JWT.encode(payload, jwk.keypair, 'RS512', headers)
|
486
|
+
|
487
|
+
# The jwk loader would fetch the set of JWKs from a trusted source
|
488
|
+
jwk_loader = ->(options) do
|
489
|
+
@cached_keys = nil if options[:invalidate] # need to reload the keys
|
490
|
+
@cached_keys ||= { keys: [jwk.export] }
|
491
|
+
end
|
492
|
+
|
493
|
+
begin
|
494
|
+
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader})
|
495
|
+
rescue JWT::JWKError
|
496
|
+
# Handle problems with the provided JWKs
|
497
|
+
rescue JWT::DecodeError
|
498
|
+
# Handle other decode related issues e.g. no kid in header, no matching public key found etc.
|
499
|
+
end
|
500
|
+
```
|
501
|
+
|
502
|
+
or by passing JWK as a simple Hash
|
503
|
+
|
504
|
+
```
|
505
|
+
jwks = { keys: [{ ... }] } # keys needs to be Symbol
|
506
|
+
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
|
507
|
+
```
|
508
|
+
|
509
|
+
### Importing and exporting JSON Web Keys
|
510
|
+
|
511
|
+
The ::JWT::JWK class can be used to import and export both the public key (default behaviour) and the private key. To include the private key in the export pass the `include_private` parameter to the export method.
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
|
515
|
+
|
516
|
+
jwk_hash = jwk.export
|
517
|
+
jwk_hash_with_private_key = jwk.export(include_private: true)
|
518
|
+
```
|
519
|
+
|
434
520
|
# Development and Tests
|
435
521
|
|
436
522
|
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
|
@@ -439,40 +525,19 @@ We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec an
|
|
439
525
|
rake release
|
440
526
|
```
|
441
527
|
|
442
|
-
The tests are written with rspec.
|
528
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
443
529
|
|
444
530
|
```bash
|
445
|
-
bundle
|
531
|
+
bundle install
|
532
|
+
bundle exec appraisal rake test
|
446
533
|
```
|
447
534
|
|
448
535
|
**If you want a release cut with your PR, please include a version bump according to [Semantic Versioning](http://semver.org/)**
|
449
536
|
|
450
537
|
## Contributors
|
451
538
|
|
452
|
-
|
453
|
-
* Ilya Zhitomirskiy <ilya@joindiaspora.com>
|
454
|
-
* Daniel Grippi <daniel@joindiaspora.com>
|
455
|
-
* Jeff Lindsay <progrium@gmail.com>
|
456
|
-
* Bob Aman <bob@sporkmonger.com>
|
457
|
-
* Micah Gates <github@mgates.com>
|
458
|
-
* Rob Wygand <rob@wygand.com>
|
459
|
-
* Ariel Salomon (Oscil8)
|
460
|
-
* Paul Battley <pbattley@gmail.com>
|
461
|
-
* Zane Shannon [@zshannon](https://github.com/zshannon)
|
462
|
-
* Brian Fletcher [@punkle](https://github.com/punkle)
|
463
|
-
* Alex [@ZhangHanDong](https://github.com/ZhangHanDong)
|
464
|
-
* John Downey [@jtdowney](https://github.com/jtdowney)
|
465
|
-
* Adam Greene [@skippy](https://github.com/skippy)
|
466
|
-
* Tim Rudat [@excpt](https://github.com/excpt) <timrudat@gmail.com> - Maintainer
|
539
|
+
See `AUTHORS` file.
|
467
540
|
|
468
541
|
## License
|
469
542
|
|
470
|
-
|
471
|
-
|
472
|
-
Copyright (c) 2011 Jeff Lindsay
|
473
|
-
|
474
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
475
|
-
|
476
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
477
|
-
|
478
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
543
|
+
See `LICENSE` file.
|