jwt 1.5.6 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +74 -0
  3. data/.gitignore +1 -1
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +95 -0
  6. data/.rubocop_todo.yml +191 -0
  7. data/.sourcelevel.yml +18 -0
  8. data/AUTHORS +101 -0
  9. data/Appraisals +10 -0
  10. data/CHANGELOG.md +349 -8
  11. data/Gemfile +2 -1
  12. data/README.md +225 -68
  13. data/Rakefile +4 -1
  14. data/lib/jwt.rb +14 -176
  15. data/lib/jwt/algos.rb +44 -0
  16. data/lib/jwt/algos/ecdsa.rb +35 -0
  17. data/lib/jwt/algos/eddsa.rb +23 -0
  18. data/lib/jwt/algos/hmac.rb +34 -0
  19. data/lib/jwt/algos/none.rb +15 -0
  20. data/lib/jwt/algos/ps.rb +43 -0
  21. data/lib/jwt/algos/rsa.rb +19 -0
  22. data/lib/jwt/algos/unsupported.rb +17 -0
  23. data/lib/jwt/base64.rb +19 -0
  24. data/lib/jwt/claims_validator.rb +35 -0
  25. data/lib/jwt/decode.rb +83 -31
  26. data/lib/jwt/default_options.rb +15 -0
  27. data/lib/jwt/encode.rb +69 -0
  28. data/lib/jwt/error.rb +6 -0
  29. data/lib/jwt/json.rb +10 -9
  30. data/lib/jwt/jwk.rb +51 -0
  31. data/lib/jwt/jwk/ec.rb +150 -0
  32. data/lib/jwt/jwk/hmac.rb +58 -0
  33. data/lib/jwt/jwk/key_base.rb +18 -0
  34. data/lib/jwt/jwk/key_finder.rb +62 -0
  35. data/lib/jwt/jwk/rsa.rb +115 -0
  36. data/lib/jwt/security_utils.rb +57 -0
  37. data/lib/jwt/signature.rb +39 -0
  38. data/lib/jwt/verify.rb +45 -53
  39. data/lib/jwt/version.rb +3 -3
  40. data/ruby-jwt.gemspec +6 -8
  41. metadata +39 -95
  42. data/.codeclimate.yml +0 -20
  43. data/.travis.yml +0 -13
  44. data/Manifest +0 -8
  45. data/spec/fixtures/certs/ec256-private.pem +0 -8
  46. data/spec/fixtures/certs/ec256-public.pem +0 -4
  47. data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
  48. data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
  49. data/spec/fixtures/certs/ec384-private.pem +0 -9
  50. data/spec/fixtures/certs/ec384-public.pem +0 -5
  51. data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
  52. data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
  53. data/spec/fixtures/certs/ec512-private.pem +0 -10
  54. data/spec/fixtures/certs/ec512-public.pem +0 -6
  55. data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
  56. data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
  57. data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
  58. data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
  59. data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
  60. data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
  61. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
  62. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
  63. data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
  64. data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
  65. data/spec/integration/readme_examples_spec.rb +0 -190
  66. data/spec/jwt/verify_spec.rb +0 -197
  67. data/spec/jwt_spec.rb +0 -240
  68. data/spec/spec_helper.rb +0 -31
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
2
1
  source 'https://rubygems.org'
3
2
 
4
3
  gemspec
4
+
5
+ gem 'rubocop', '~> 0.52.0' # Same as codeclimate default
data/README.md CHANGED
@@ -1,24 +1,32 @@
1
1
  # JWT
2
2
 
3
- [![Build Status](https://travis-ci.org/jwt/ruby-jwt.svg)](https://travis-ci.org/jwt/ruby-jwt)
3
+ [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
4
+ [![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=master)](https://github.com/jwt/ruby-jwt/actions)
4
5
  [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
5
6
  [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
6
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)
7
9
 
8
- A pure ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
10
+ A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
9
11
 
10
- If you have further questions releated to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
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).
11
13
 
12
14
  ## Announcements
13
15
 
14
- * Ruby 1.9.3 support will be dropped by December 31st, 2016.
16
+ * Ruby 1.9.3 support was dropped at December 31st, 2016.
15
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)
16
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
+
17
25
  ## Installing
18
26
 
19
27
  ### Using Rubygems:
20
28
  ```bash
21
- sudo gem install jwt
29
+ gem install jwt
22
30
  ```
23
31
 
24
32
  ### Using Bundler:
@@ -30,7 +38,7 @@ And run `bundle install`
30
38
 
31
39
  ## Algorithms and Usage
32
40
 
33
- 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**
34
42
 
35
43
  See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
36
44
 
@@ -41,12 +49,12 @@ See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values
41
49
  ```ruby
42
50
  require 'jwt'
43
51
 
44
- payload = {:data => 'test'}
52
+ payload = { data: 'test' }
45
53
 
46
54
  # IMPORTANT: set nil as password parameter
47
55
  token = JWT.encode payload, nil, 'none'
48
56
 
49
- # eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
57
+ # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
50
58
  puts token
51
59
 
52
60
  # Set password to nil and validation to false otherwise this won't work
@@ -55,14 +63,15 @@ decoded_token = JWT.decode token, nil, false
55
63
  # Array
56
64
  # [
57
65
  # {"data"=>"test"}, # payload
58
- # {"typ"=>"JWT", "alg"=>"none"} # header
66
+ # {"alg"=>"none"} # header
59
67
  # ]
60
68
  puts decoded_token
61
69
  ```
62
70
 
63
- **HMAC** (default: HS256)
71
+ **HMAC**
64
72
 
65
- * HS256 - HMAC using SHA-256 hash algorithm (default)
73
+ * HS256 - HMAC using SHA-256 hash algorithm
74
+ * HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
66
75
  * HS384 - HMAC using SHA-384 hash algorithm
67
76
  * HS512 - HMAC using SHA-512 hash algorithm
68
77
 
@@ -71,19 +80,40 @@ hmac_secret = 'my$ecretK3y'
71
80
 
72
81
  token = JWT.encode payload, hmac_secret, 'HS256'
73
82
 
74
- # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY
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
75
99
  puts token
76
100
 
77
- decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
101
+ decoded_token = JWT.decode token, nil, true, { algorithm: 'HS256' }
78
102
 
79
103
  # Array
80
104
  # [
81
105
  # {"data"=>"test"}, # payload
82
- # {"typ"=>"JWT", "alg"=>"HS256"} # header
106
+ # {"alg"=>"HS256"} # header
83
107
  # ]
84
108
  puts decoded_token
85
109
  ```
86
110
 
111
+ Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl enforces a maximum key size of 32 bytes for these algorithms.
112
+
113
+ [RbNaCl](https://github.com/cryptosphere/rbnacl) requires
114
+ [libsodium](https://github.com/jedisct1/libsodium), it can be installed
115
+ on MacOS with `brew install libsodium`.
116
+
87
117
  **RSA**
88
118
 
89
119
  * RS256 - RSA using SHA-256 hash algorithm
@@ -96,15 +126,15 @@ rsa_public = rsa_private.public_key
96
126
 
97
127
  token = JWT.encode payload, rsa_private, 'RS256'
98
128
 
99
- # eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0ZXN0IjoiZGF0YSJ9.c2FynXNyi6_PeKxrDGxfS3OLwQ8lTDbWBWdq7oMviCy2ZfFpzvW2E_odCWJrbLof-eplHCsKzW7MGAntHMALXgclm_Cs9i2Exi6BZHzpr9suYkrhIjwqV1tCgMBCQpdeMwIq6SyKVjgH3L51ivIt0-GDDPDH1Rcut3jRQzp3Q35bg3tcI2iVg7t3Msvl9QrxXAdYNFiS5KXH22aJZ8X_O2HgqVYBXfSB1ygTYUmKTIIyLbntPQ7R22rFko1knGWOgQCoYXwbtpuKRZVFrxX958L2gUWgb4jEQNf3fhOtkBm1mJpj-7BGst00o8g_3P2zHy-3aKgpPo1XlKQGjRrrxA
129
+ # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
100
130
  puts token
101
131
 
102
- decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
132
+ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
103
133
 
104
134
  # Array
105
135
  # [
106
136
  # {"data"=>"test"}, # payload
107
- # {"typ"=>"JWT", "alg"=>"RS256"} # header
137
+ # {"alg"=>"RS256"} # header
108
138
  # ]
109
139
  puts decoded_token
110
140
  ```
@@ -123,22 +153,78 @@ ecdsa_public.private_key = nil
123
153
 
124
154
  token = JWT.encode payload, ecdsa_key, 'ES256'
125
155
 
126
- # eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ0ZXN0IjoiZGF0YSJ9.MEQCIAtShrxRwP1L9SapqaT4f7hajDJH4t_rfm-YlZcNDsBNAiB64M4-JRfyS8nRMlywtQ9lHbvvec9U54KznzOe1YxTyA
156
+ # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
127
157
  puts token
128
158
 
129
- decoded_token = JWT.decode token, ecdsa_public, true, { :algorithm => 'ES256' }
159
+ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
130
160
 
131
161
  # Array
132
162
  # [
133
163
  # {"test"=>"data"}, # payload
134
- # {"typ"=>"JWT", "alg"=>"ES256"} # header
164
+ # {"alg"=>"ES256"} # header
135
165
  # ]
136
166
  puts decoded_token
137
167
  ```
138
168
 
169
+ **EDDSA**
170
+
171
+ In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
172
+
173
+ ```ruby
174
+ gem 'rbnacl'
175
+ ```
176
+
177
+ For more detailed installation instruction check the official [repository](https://github.com/cryptosphere/rbnacl) on GitHub.
178
+
179
+ * ED25519
180
+
181
+ ```ruby
182
+ private_key = RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF')
183
+ public_key = private_key.verify_key
184
+ token = JWT.encode payload, private_key, 'ED25519'
185
+
186
+ # eyJhbGciOiJFRDI1NTE5In0.eyJkYXRhIjoidGVzdCJ9.6xIztXyOupskddGA_RvKU76V9b2dCQUYhoZEVFnRimJoPYIzZ2Fm47CWw8k2NTCNpgfAuxg9OXjaiVK7MvrbCQ
187
+ puts token
188
+
189
+ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
190
+ # Array
191
+ # [
192
+ # {"test"=>"data"}, # payload
193
+ # {"alg"=>"ED25519"} # header
194
+ # ]
195
+
196
+ ```
197
+
139
198
  **RSASSA-PSS**
140
199
 
141
- Not implemented.
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
+ ```
142
228
 
143
229
  ## Support for reserved claim names
144
230
  JSON Web Token defines some reserved claim names and defines how they should be
@@ -152,6 +238,38 @@ used. JWT supports these reserved claim names:
152
238
  - 'iat' (Issued At) Claim
153
239
  - 'sub' (Subject) Claim
154
240
 
241
+ ## Add custom header fields
242
+ Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
243
+ To add custom header fields you need to pass `header_fields` parameter
244
+
245
+ ```ruby
246
+ token = JWT.encode payload, key, algorithm='HS256', header_fields={}
247
+ ```
248
+
249
+ **Example:**
250
+
251
+ ```ruby
252
+ require 'jwt'
253
+
254
+ payload = { data: 'test' }
255
+
256
+ # IMPORTANT: set nil as password parameter
257
+ token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
258
+
259
+ # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
260
+ puts token
261
+
262
+ # Set password to nil and validation to false otherwise this won't work
263
+ decoded_token = JWT.decode token, nil, false
264
+
265
+ # Array
266
+ # [
267
+ # {"data"=>"test"}, # payload
268
+ # {"typ"=>"JWT", "alg"=>"none"} # header
269
+ # ]
270
+ puts decoded_token
271
+ ```
272
+
155
273
  ### Expiration Time Claim
156
274
 
157
275
  From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
@@ -162,31 +280,37 @@ From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.i
162
280
 
163
281
  ```ruby
164
282
  exp = Time.now.to_i + 4 * 3600
165
- exp_payload = { :data => 'data', :exp => exp }
283
+ exp_payload = { data: 'data', exp: exp }
166
284
 
167
285
  token = JWT.encode exp_payload, hmac_secret, 'HS256'
168
286
 
169
287
  begin
170
- decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
288
+ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
171
289
  rescue JWT::ExpiredSignature
172
290
  # Handle expired token, e.g. logout user or deny access
173
291
  end
174
292
  ```
175
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
+
176
300
  **Adding Leeway**
177
301
 
178
302
  ```ruby
179
303
  exp = Time.now.to_i - 10
180
304
  leeway = 30 # seconds
181
305
 
182
- exp_payload = { :data => 'data', :exp => exp }
306
+ exp_payload = { data: 'data', exp: exp }
183
307
 
184
308
  # build expired token
185
309
  token = JWT.encode exp_payload, hmac_secret, 'HS256'
186
310
 
187
311
  begin
188
312
  # add leeway to ensure the token is still accepted
189
- decoded_token = JWT.decode token, hmac_secret, true, { :leeway => leeway, :algorithm => 'HS256' }
313
+ decoded_token = JWT.decode token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }
190
314
  rescue JWT::ExpiredSignature
191
315
  # Handle expired token, e.g. logout user or deny access
192
316
  end
@@ -202,31 +326,37 @@ From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.o
202
326
 
203
327
  ```ruby
204
328
  nbf = Time.now.to_i - 3600
205
- nbf_payload = { :data => 'data', :nbf => nbf }
329
+ nbf_payload = { data: 'data', nbf: nbf }
206
330
 
207
331
  token = JWT.encode nbf_payload, hmac_secret, 'HS256'
208
332
 
209
333
  begin
210
- decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
334
+ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
211
335
  rescue JWT::ImmatureSignature
212
336
  # Handle invalid token, e.g. logout user or deny access
213
337
  end
214
338
  ```
215
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
+
216
346
  **Adding Leeway**
217
347
 
218
348
  ```ruby
219
349
  nbf = Time.now.to_i + 10
220
350
  leeway = 30
221
351
 
222
- nbf_payload = { :data => 'data', :nbf => nbf }
352
+ nbf_payload = { data: 'data', nbf: nbf }
223
353
 
224
354
  # build expired token
225
355
  token = JWT.encode nbf_payload, hmac_secret, 'HS256'
226
356
 
227
357
  begin
228
358
  # add leeway to ensure the token is valid
229
- decoded_token = JWT.decode token, hmac_secret, true, { :leeway => leeway, :algorithm => 'HS256' }
359
+ decoded_token = JWT.decode token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }
230
360
  rescue JWT::ImmatureSignature
231
361
  # Handle invalid token, e.g. logout user or deny access
232
362
  end
@@ -238,15 +368,17 @@ From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/h
238
368
 
239
369
  > 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.
240
370
 
371
+ You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
372
+
241
373
  ```ruby
242
374
  iss = 'My Awesome Company Inc. or https://my.awesome.website/'
243
- iss_payload = { :data => 'data', :iss => iss }
375
+ iss_payload = { data: 'data', iss: iss }
244
376
 
245
377
  token = JWT.encode iss_payload, hmac_secret, 'HS256'
246
378
 
247
379
  begin
248
380
  # Add iss to the validation to check if the token has been manipulated
249
- decoded_token = JWT.decode token, hmac_secret, true, { :iss => iss, :verify_iss => true, :algorithm => 'HS256' }
381
+ decoded_token = JWT.decode token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }
250
382
  rescue JWT::InvalidIssuerError
251
383
  # Handle invalid token, e.g. logout user or deny access
252
384
  end
@@ -260,13 +392,13 @@ From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org
260
392
 
261
393
  ```ruby
262
394
  aud = ['Young', 'Old']
263
- aud_payload = { :data => 'data', :aud => aud }
395
+ aud_payload = { data: 'data', aud: aud }
264
396
 
265
397
  token = JWT.encode aud_payload, hmac_secret, 'HS256'
266
398
 
267
399
  begin
268
400
  # Add aud to the validation to check if the token has been manipulated
269
- decoded_token = JWT.decode token, hmac_secret, true, { :aud => aud, :verify_aud => true, :algorithm => 'HS256' }
401
+ decoded_token = JWT.decode token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }
270
402
  rescue JWT::InvalidAudError
271
403
  # Handle invalid token, e.g. logout user or deny access
272
404
  puts 'Audience Error'
@@ -283,37 +415,40 @@ From [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/h
283
415
  # Use the secret and iat to create a unique key per request to prevent replay attacks
284
416
  jti_raw = [hmac_secret, iat].join(':').to_s
285
417
  jti = Digest::MD5.hexdigest(jti_raw)
286
- jti_payload = { :data => 'data', :iat => iat, :jti => jti }
418
+ jti_payload = { data: 'data', iat: iat, jti: jti }
287
419
 
288
420
  token = JWT.encode jti_payload, hmac_secret, 'HS256'
289
421
 
290
422
  begin
291
423
  # If :verify_jti is true, validation will pass if a JTI is present
292
- #decoded_token = JWT.decode token, hmac_secret, true, { :verify_jti => true, :algorithm => 'HS256' }
424
+ #decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }
293
425
  # Alternatively, pass a proc with your own code to check if the JTI has already been used
294
- decoded_token = JWT.decode token, hmac_secret, true, { :verify_jti => proc { |jti| my_validation_method(jti) }, :algorithm => 'HS256' }
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' }
295
429
  rescue JWT::InvalidJtiError
296
430
  # Handle invalid token, e.g. logout user or deny access
297
431
  puts 'Error'
298
432
  end
299
-
300
433
  ```
301
434
 
302
435
  ### Issued At Claim
303
436
 
304
437
  From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
305
438
 
306
- > 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.
440
+
441
+ **Handle Issued At Claim**
307
442
 
308
443
  ```ruby
309
444
  iat = Time.now.to_i
310
- iat_payload = { :data => 'data', :iat => iat }
445
+ iat_payload = { data: 'data', iat: iat }
311
446
 
312
447
  token = JWT.encode iat_payload, hmac_secret, 'HS256'
313
448
 
314
449
  begin
315
450
  # Add iat to the validation to check if the token has been manipulated
316
- decoded_token = JWT.decode token, hmac_secret, true, { :verify_iat => true, :algorithm => 'HS256' }
451
+ decoded_token = JWT.decode token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }
317
452
  rescue JWT::InvalidIatError
318
453
  # Handle invalid token, e.g. logout user or deny access
319
454
  end
@@ -327,18 +462,61 @@ From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/
327
462
 
328
463
  ```ruby
329
464
  sub = 'Subject'
330
- sub_payload = { :data => 'data', :sub => sub }
465
+ sub_payload = { data: 'data', sub: sub }
331
466
 
332
467
  token = JWT.encode sub_payload, hmac_secret, 'HS256'
333
468
 
334
469
  begin
335
470
  # Add sub to the validation to check if the token has been manipulated
336
- decoded_token = JWT.decode token, hmac_secret, true, { 'sub' => sub, :verify_sub => true, :algorithm => 'HS256' }
471
+ decoded_token = JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }
337
472
  rescue JWT::InvalidSubError
338
473
  # Handle invalid token, e.g. logout user or deny access
339
474
  end
340
475
  ```
341
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
+
342
520
  # Development and Tests
343
521
 
344
522
  We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
@@ -347,40 +525,19 @@ We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec an
347
525
  rake release
348
526
  ```
349
527
 
350
- The tests are written with rspec. Given you have installed the dependencies via bundler, you can run tests with
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.
351
529
 
352
530
  ```bash
353
- bundle exec rspec
531
+ bundle install
532
+ bundle exec appraisal rake test
354
533
  ```
355
534
 
356
535
  **If you want a release cut with your PR, please include a version bump according to [Semantic Versioning](http://semver.org/)**
357
536
 
358
537
  ## Contributors
359
538
 
360
- * Jordan Brough <github.jordanb@xoxy.net>
361
- * Ilya Zhitomirskiy <ilya@joindiaspora.com>
362
- * Daniel Grippi <daniel@joindiaspora.com>
363
- * Jeff Lindsay <progrium@gmail.com>
364
- * Bob Aman <bob@sporkmonger.com>
365
- * Micah Gates <github@mgates.com>
366
- * Rob Wygand <rob@wygand.com>
367
- * Ariel Salomon (Oscil8)
368
- * Paul Battley <pbattley@gmail.com>
369
- * Zane Shannon [@zshannon](https://github.com/zshannon)
370
- * Brian Fletcher [@punkle](https://github.com/punkle)
371
- * Alex [@ZhangHanDong](https://github.com/ZhangHanDong)
372
- * John Downey [@jtdowney](https://github.com/jtdowney)
373
- * Adam Greene [@skippy](https://github.com/skippy)
374
- * Tim Rudat [@excpt](https://github.com/excpt) <timrudat@gmail.com> - Maintainer
539
+ See `AUTHORS` file.
375
540
 
376
541
  ## License
377
542
 
378
- MIT
379
-
380
- Copyright (c) 2011 Jeff Lindsay
381
-
382
- 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:
383
-
384
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
385
-
386
- 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.