jwt 2.9.3 → 3.1.2

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -47
  3. data/CODE_OF_CONDUCT.md +14 -14
  4. data/CONTRIBUTING.md +9 -10
  5. data/README.md +273 -234
  6. data/UPGRADING.md +47 -0
  7. data/lib/jwt/base64.rb +4 -10
  8. data/lib/jwt/claims/audience.rb +10 -0
  9. data/lib/jwt/claims/crit.rb +35 -0
  10. data/lib/jwt/claims/decode_verifier.rb +3 -3
  11. data/lib/jwt/claims/expiration.rb +10 -0
  12. data/lib/jwt/claims/issued_at.rb +7 -0
  13. data/lib/jwt/claims/issuer.rb +10 -0
  14. data/lib/jwt/claims/jwt_id.rb +10 -0
  15. data/lib/jwt/claims/not_before.rb +10 -0
  16. data/lib/jwt/claims/numeric.rb +9 -19
  17. data/lib/jwt/claims/required.rb +10 -0
  18. data/lib/jwt/claims/subject.rb +10 -0
  19. data/lib/jwt/claims/verifier.rb +6 -7
  20. data/lib/jwt/claims.rb +4 -19
  21. data/lib/jwt/configuration/container.rb +20 -1
  22. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  23. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  24. data/lib/jwt/configuration.rb +8 -0
  25. data/lib/jwt/decode.rb +42 -79
  26. data/lib/jwt/encode.rb +17 -56
  27. data/lib/jwt/encoded_token.rb +236 -0
  28. data/lib/jwt/error.rb +32 -1
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/ecdsa.rb +31 -13
  31. data/lib/jwt/jwa/hmac.rb +2 -7
  32. data/lib/jwt/jwa/none.rb +1 -0
  33. data/lib/jwt/jwa/ps.rb +3 -3
  34. data/lib/jwt/jwa/rsa.rb +6 -6
  35. data/lib/jwt/jwa/signing_algorithm.rb +3 -1
  36. data/lib/jwt/jwa/unsupported.rb +1 -0
  37. data/lib/jwt/jwa.rb +77 -24
  38. data/lib/jwt/jwk/ec.rb +54 -65
  39. data/lib/jwt/jwk/hmac.rb +5 -6
  40. data/lib/jwt/jwk/key_base.rb +16 -1
  41. data/lib/jwt/jwk/key_finder.rb +35 -8
  42. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  43. data/lib/jwt/jwk/rsa.rb +7 -4
  44. data/lib/jwt/jwk/set.rb +2 -0
  45. data/lib/jwt/jwk.rb +1 -1
  46. data/lib/jwt/token.rb +131 -0
  47. data/lib/jwt/version.rb +24 -19
  48. data/lib/jwt.rb +17 -7
  49. data/ruby-jwt.gemspec +2 -0
  50. metadata +36 -16
  51. data/lib/jwt/claims_validator.rb +0 -16
  52. data/lib/jwt/deprecations.rb +0 -48
  53. data/lib/jwt/jwa/compat.rb +0 -29
  54. data/lib/jwt/jwa/eddsa.rb +0 -34
  55. data/lib/jwt/jwa/hmac_rbnacl.rb +0 -49
  56. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
  57. data/lib/jwt/jwa/wrapper.rb +0 -43
  58. data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
  59. data/lib/jwt/verify.rb +0 -34
data/README.md CHANGED
@@ -1,224 +1,157 @@
1
1
  # JWT
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
4
- [![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
5
- [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
6
- [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
7
- [![Issue Count](https://codeclimate.com/github/jwt/ruby-jwt/badges/issue_count.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
4
+ [![Build Status](https://github.com/jwt/ruby-jwt/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
5
+ [![Maintainability](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/maintainability.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt)
6
+ [![Code Coverage](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/test_coverage.svg)](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
- ## Announcements
14
- * Ruby 2.4 support was dropped in version 2.4.0
15
- * Ruby 1.9.3 support was dropped at December 31st, 2016.
16
- * Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
17
-
18
- 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
- |![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)|
16
+ | Logo | Message |
17
+ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
18
+ | ![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) |
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
- ## Algorithms and Usage
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
- Deprecation warnings are logged once (`:once` option) by default to avoid spam in logs. Other options are `:silent` to completely silence warnings and `:warn` to log every time a deprecated path is executed.
38
+ Finally require the gem in your application
49
39
 
50
40
  ```ruby
51
- JWT.configuration.deprecation_warnings = :warn # default is :once
41
+ require 'jwt'
52
42
  ```
53
43
 
54
- ### Base64 decoding
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
- In the past the gem has been supporting the Base64 decoding specified in [RFC2045](https://www.rfc-editor.org/rfc/rfc2045) allowing newlines and blanks in the base64 encoded payload. In future versions base64 decoding will be stricter and only comply to [RFC4648](https://www.rfc-editor.org/rfc/rfc4648).
48
+ Additionally the EdDSA algorithm is supported via a the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
57
49
 
58
- The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
59
- ```ruby
60
- JWT.configuration.strict_base64_decoding = true # default is false
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
- * none - unsigned token
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
- # IMPORTANT: set nil as password parameter
73
- token = JWT.encode payload, nil, 'none'
74
-
75
- # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
76
- puts token
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
- * HS256 - HMAC using SHA-256 hash algorithm
92
- * HS384 - HMAC using SHA-384 hash algorithm
93
- * HS512 - HMAC using SHA-512 hash algorithm
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
- # The secret must be a string. With OpenSSL 3.0/openssl gem `<3.0.1`, JWT::DecodeError will be raised if it isn't provided.
77
+ payload = { data: 'test' }
97
78
  hmac_secret = 'my$ecretK3y'
98
79
 
99
- token = JWT.encode payload, hmac_secret, 'HS256'
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
- # Array
107
- # [
108
- # {"data"=>"test"}, # payload
109
- # {"alg"=>"HS256"} # header
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
- * RS256 - RSA using SHA-256 hash algorithm
117
- * RS384 - RSA using SHA-384 hash algorithm
118
- * RS512 - RSA using SHA-512 hash algorithm
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
- rsa_private = OpenSSL::PKey::RSA.generate 2048
122
- rsa_public = rsa_private.public_key
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 payload, rsa_private, 'RS256'
101
+ token = JWT.encode(payload, rsa_private, 'RS256')
102
+ # => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
125
103
 
126
- # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
127
- puts token
128
-
129
- decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
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
- * ES256 - ECDSA using P-256 and SHA-256
142
- * ES384 - ECDSA using P-384 and SHA-384
143
- * ES512 - ECDSA using P-521 and SHA-512
144
- * ES256K - ECDSA using P-256K and SHA-256
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 payload, ecdsa_key, 'ES256'
150
-
151
- # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
152
- puts token
122
+ token = JWT.encode(payload, ecdsa_key, 'ES256')
123
+ # => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
153
124
 
154
- decoded_token = JWT.decode token, ecdsa_key, true, { algorithm: 'ES256' }
155
-
156
- # Array
157
- # [
158
- # {"test"=>"data"}, # payload
159
- # {"alg"=>"ES256"} # header
160
- # ]
161
- puts decoded_token
125
+ decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
126
+ # => [
127
+ # {"test"=>"data"}, # payload
128
+ # {"alg"=>"ES256"} # header
129
+ # ]
162
130
  ```
163
131
 
164
- ### **EDDSA**
165
-
166
- In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
167
-
168
- ```ruby
169
- gem 'rbnacl'
170
- ```
171
-
172
- For more detailed installation instruction check the official [repository](https://github.com/RubyCrypto/rbnacl) on GitHub.
173
-
174
- * ED25519
132
+ ### **EdDSA**
175
133
 
176
- ```ruby
177
- private_key = RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF')
178
- public_key = private_key.verify_key
179
- token = JWT.encode payload, private_key, 'ED25519'
180
-
181
- # eyJhbGciOiJFRDI1NTE5In0.eyJkYXRhIjoidGVzdCJ9.6xIztXyOupskddGA_RvKU76V9b2dCQUYhoZEVFnRimJoPYIzZ2Fm47CWw8k2NTCNpgfAuxg9OXjaiVK7MvrbCQ
182
- puts token
183
-
184
- decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
185
- # Array
186
- # [
187
- # {"test"=>"data"}, # payload
188
- # {"alg"=>"ED25519"} # header
189
- # ]
190
-
191
- ```
134
+ Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa).
192
135
 
193
136
  ### **RSASSA-PSS**
194
137
 
195
- In order to use this algorithm you need to add the `openssl` gem to your `Gemfile` with a version greater or equal to `2.1`.
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
196
141
 
197
142
  ```ruby
198
- gem 'openssl', '~> 2.1'
199
- ```
143
+ payload = { data: 'test' }
144
+ rsa_private = OpenSSL::PKey::RSA.generate(2048)
145
+ rsa_public = rsa_private.public_key
200
146
 
201
- * PS256 - RSASSA-PSS using SHA-256 hash algorithm
202
- * PS384 - RSASSA-PSS using SHA-384 hash algorithm
203
- * PS512 - RSASSA-PSS using SHA-512 hash algorithm
147
+ token = JWT.encode(payload, rsa_private, 'PS256')
148
+ # => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
204
149
 
205
- ```ruby
206
- rsa_private = OpenSSL::PKey::RSA.generate 2048
207
- rsa_public = rsa_private.public_key
208
-
209
- token = JWT.encode payload, rsa_private, 'PS256'
210
-
211
- # eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.KEmqagMUHM-NcmXo6818ZazVTIAkn9qU9KQFT1c5Iq91n0KRpAI84jj4ZCdkysDlWokFs3Dmn4MhcXP03oJKLFgnoPL40_Wgg9iFr0jnIVvnMUp1kp2RFUbL0jqExGTRA3LdAhuvw6ZByGD1bkcWjDXygjQw-hxILrT1bENjdr0JhFd-cB0-ps5SB0mwhFNcUw-OM3Uu30B1-mlFaelUY8jHJYKwLTZPNxHzndt8RGXF8iZLp7dGb06HSCKMcVzhASGMH4ZdFystRe2hh31cwcvnl-Eo_D4cdwmpN3Abhk_8rkxawQJR3duh8HNKc4AyFPo7SabEaSu2gLnLfN3yfg
212
- puts token
213
-
214
- decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
215
-
216
- # Array
217
- # [
218
- # {"data"=>"test"}, # payload
219
- # {"alg"=>"PS256"} # header
220
- # ]
221
- puts decoded_token
150
+ decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
151
+ # => [
152
+ # {"data"=>"test"}, # payload
153
+ # {"alg"=>"PS256"} # header
154
+ # ]
222
155
  ```
223
156
 
224
157
  ### **Custom algorithms**
@@ -230,7 +163,6 @@ When encoding or decoding a token, you can pass in a custom object through the `
230
163
 
231
164
  For customization options check the details from `JWT::JWA::SigningAlgorithm`.
232
165
 
233
-
234
166
  ```ruby
235
167
  module CustomHS512Algorithm
236
168
  extend JWT::JWA::SigningAlgorithm
@@ -248,82 +180,191 @@ module CustomHS512Algorithm
248
180
  end
249
181
  end
250
182
 
251
- token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
252
- payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
253
- ```
183
+ payload = { data: 'test' }
184
+ token = JWT.encode(payload, 'secret', CustomHS512Algorithm)
185
+ # => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
254
186
 
255
- ## Support for reserved claim names
256
- JSON Web Token defines some reserved claim names and defines how they should be
257
- used. JWT supports these reserved claim names:
187
+ decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
188
+ # => [
189
+ # {"data"=>"test"}, # payload
190
+ # {"alg"=>"HS512"} # header
191
+ # ]
192
+ ```
258
193
 
259
- - 'exp' (Expiration Time) Claim
260
- - 'nbf' (Not Before Time) Claim
261
- - 'iss' (Issuer) Claim
262
- - 'aud' (Audience) Claim
263
- - 'jti' (JWT ID) Claim
264
- - 'iat' (Issued At) Claim
265
- - 'sub' (Subject) Claim
194
+ ### Add custom header fields
266
195
 
267
- ## Add custom header fields
268
- Ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
196
+ The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5)
269
197
  To add custom header fields you need to pass `header_fields` parameter
270
198
 
271
199
  ```ruby
272
- token = JWT.encode payload, key, algorithm='HS256', header_fields={}
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
+ # ]
210
+ ```
211
+
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
217
+
218
+ ```ruby
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"
273
227
  ```
274
228
 
275
- **Example:**
229
+ ### Verifying and decoding a token
230
+
231
+ The `JWT::EncodedToken` can be used as a token object that allows verification of signatures and claims.
276
232
 
277
233
  ```ruby
278
- require 'jwt'
234
+ encoded_token = JWT::EncodedToken.new(token.jwt)
279
235
 
280
- payload = { data: 'test' }
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'}
243
+ ```
244
+
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.
246
+
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.
255
+
256
+ ```ruby
257
+ jwk_json = '{
258
+ "kty": "oct",
259
+ "k": "c2VjcmV0",
260
+ "alg": "HS256",
261
+ "kid": "hmac"
262
+ }'
263
+
264
+ jwk = JWT::JWK.import(JSON.parse(jwk_json))
265
+
266
+ token = JWT::Token.new(payload: payload, header: header)
267
+
268
+ token.sign!(key: jwk, algorithm: 'HS256')
281
269
 
282
- # IMPORTANT: set nil as password parameter
283
- token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
270
+ encoded_token = JWT::EncodedToken.new(token.jwt)
271
+ encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk})
272
+ ```
273
+
274
+ #### Using a keyfinder
284
275
 
285
- # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9.
286
- puts token
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.
287
277
 
288
- # Set password to nil and validation to false otherwise this won't work
289
- decoded_token = JWT.decode token, nil, false
278
+ An example on using the built-in JWK keyfinder.
279
+
280
+ ```ruby
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)
290
285
 
291
- # Array
292
- # [
293
- # {"data"=>"test"}, # payload
294
- # {"typ"=>"JWT", "alg"=>"none"} # header
295
- # ]
296
- puts decoded_token
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' }
297
291
  ```
298
292
 
293
+ Using a custom keyfinder proc.
294
+
295
+ ```ruby
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)
300
+
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
+ ```
306
+
307
+ ### Detached payload
308
+
309
+ The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT.
310
+
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.
320
+
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"}
326
+ ```
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
+
299
341
  ### Expiration Time Claim
300
342
 
301
343
  From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
302
344
 
303
- > 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.
304
-
305
- **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.
306
346
 
307
347
  ```ruby
308
348
  exp = Time.now.to_i + 4 * 3600
309
349
  exp_payload = { data: 'data', exp: exp }
310
350
 
311
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
351
+ token = JWT.encode(exp_payload, hmac_secret, 'HS256')
312
352
 
313
353
  begin
314
- decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
354
+ decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
315
355
  rescue JWT::ExpiredSignature
316
356
  # Handle expired token, e.g. logout user or deny access
317
357
  end
318
358
  ```
319
359
 
320
360
  The Expiration Claim verification can be disabled.
361
+
321
362
  ```ruby
322
363
  # Decode token without raising JWT::ExpiredSignature error
323
- JWT.decode token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }
364
+ JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
324
365
  ```
325
366
 
326
- **Adding Leeway**
367
+ Leeway and the exp claim.
327
368
 
328
369
  ```ruby
329
370
  exp = Time.now.to_i - 10
@@ -332,11 +373,11 @@ leeway = 30 # seconds
332
373
  exp_payload = { data: 'data', exp: exp }
333
374
 
334
375
  # build expired token
335
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
376
+ token = JWT.encode(exp_payload, hmac_secret, 'HS256')
336
377
 
337
378
  begin
338
379
  # add leeway to ensure the token is still accepted
339
- decoded_token = JWT.decode token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }
380
+ decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
340
381
  rescue JWT::ExpiredSignature
341
382
  # Handle expired token, e.g. logout user or deny access
342
383
  end
@@ -346,30 +387,29 @@ end
346
387
 
347
388
  From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5):
348
389
 
349
- > 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.
350
-
351
- **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.
352
391
 
353
392
  ```ruby
354
393
  nbf = Time.now.to_i - 3600
355
394
  nbf_payload = { data: 'data', nbf: nbf }
356
395
 
357
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
396
+ token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
358
397
 
359
398
  begin
360
- decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
399
+ decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
361
400
  rescue JWT::ImmatureSignature
362
401
  # Handle invalid token, e.g. logout user or deny access
363
402
  end
364
403
  ```
365
404
 
366
405
  The Not Before Claim verification can be disabled.
406
+
367
407
  ```ruby
368
408
  # Decode token without raising JWT::ImmatureSignature error
369
- JWT.decode token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }
409
+ JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
370
410
  ```
371
411
 
372
- **Adding Leeway**
412
+ Leeway and the nbf claim.
373
413
 
374
414
  ```ruby
375
415
  nbf = Time.now.to_i + 10
@@ -378,11 +418,11 @@ leeway = 30
378
418
  nbf_payload = { data: 'data', nbf: nbf }
379
419
 
380
420
  # build expired token
381
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
421
+ token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
382
422
 
383
423
  begin
384
424
  # add leeway to ensure the token is valid
385
- decoded_token = JWT.decode token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }
425
+ decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
386
426
  rescue JWT::ImmatureSignature
387
427
  # Handle invalid token, e.g. logout user or deny access
388
428
  end
@@ -392,7 +432,7 @@ end
392
432
 
393
433
  From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1):
394
434
 
395
- > 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.
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.
396
436
 
397
437
  You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
398
438
 
@@ -400,11 +440,11 @@ You can pass multiple allowed issuers as an Array, verification will pass if one
400
440
  iss = 'My Awesome Company Inc. or https://my.awesome.website/'
401
441
  iss_payload = { data: 'data', iss: iss }
402
442
 
403
- token = JWT.encode iss_payload, hmac_secret, 'HS256'
443
+ token = JWT.encode(iss_payload, hmac_secret, 'HS256')
404
444
 
405
445
  begin
406
446
  # Add iss to the validation to check if the token has been manipulated
407
- decoded_token = JWT.decode token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }
447
+ decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
408
448
  rescue JWT::InvalidIssuerError
409
449
  # Handle invalid token, e.g. logout user or deny access
410
450
  end
@@ -415,24 +455,24 @@ On supported ruby versions (>= 2.5) you can also delegate to methods, on older v
415
455
  to convert them to proc (using `to_proc`)
416
456
 
417
457
  ```ruby
418
- JWT.decode token, hmac_secret, true,
458
+ JWT.decode(token, hmac_secret, true,
419
459
  iss: %r'https://my.awesome.website/',
420
460
  verify_iss: true,
421
- algorithm: 'HS256'
461
+ algorithm: 'HS256')
422
462
  ```
423
463
 
424
464
  ```ruby
425
- JWT.decode token, hmac_secret, true,
465
+ JWT.decode(token, hmac_secret, true,
426
466
  iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
427
467
  verify_iss: true,
428
- algorithm: 'HS256'
468
+ algorithm: 'HS256')
429
469
  ```
430
470
 
431
471
  ```ruby
432
- JWT.decode token, hmac_secret, true,
472
+ JWT.decode(token, hmac_secret, true,
433
473
  iss: method(:valid_issuer?),
434
474
  verify_iss: true,
435
- algorithm: 'HS256'
475
+ algorithm: 'HS256')
436
476
 
437
477
  # somewhere in the same class:
438
478
  def valid_issuer?(issuer)
@@ -444,17 +484,17 @@ end
444
484
 
445
485
  From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
446
486
 
447
- > 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.
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.
448
488
 
449
489
  ```ruby
450
490
  aud = ['Young', 'Old']
451
491
  aud_payload = { data: 'data', aud: aud }
452
492
 
453
- token = JWT.encode aud_payload, hmac_secret, 'HS256'
493
+ token = JWT.encode(aud_payload, hmac_secret, 'HS256')
454
494
 
455
495
  begin
456
496
  # Add aud to the validation to check if the token has been manipulated
457
- decoded_token = JWT.decode token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }
497
+ decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
458
498
  rescue JWT::InvalidAudError
459
499
  # Handle invalid token, e.g. logout user or deny access
460
500
  puts 'Audience Error'
@@ -473,15 +513,15 @@ jti_raw = [hmac_secret, iat].join(':').to_s
473
513
  jti = Digest::MD5.hexdigest(jti_raw)
474
514
  jti_payload = { data: 'data', iat: iat, jti: jti }
475
515
 
476
- token = JWT.encode jti_payload, hmac_secret, 'HS256'
516
+ token = JWT.encode(jti_payload, hmac_secret, 'HS256')
477
517
 
478
518
  begin
479
519
  # If :verify_jti is true, validation will pass if a JTI is present
480
- #decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }
520
+ #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
481
521
  # Alternatively, pass a proc with your own code to check if the JTI has already been used
482
- decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }
522
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
483
523
  # or
484
- decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }
524
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
485
525
  rescue JWT::InvalidJtiError
486
526
  # Handle invalid token, e.g. logout user or deny access
487
527
  puts 'Error'
@@ -492,19 +532,17 @@ end
492
532
 
493
533
  From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
494
534
 
495
- > 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.
496
-
497
- **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.
498
536
 
499
537
  ```ruby
500
538
  iat = Time.now.to_i
501
539
  iat_payload = { data: 'data', iat: iat }
502
540
 
503
- token = JWT.encode iat_payload, hmac_secret, 'HS256'
541
+ token = JWT.encode(iat_payload, hmac_secret, 'HS256')
504
542
 
505
543
  begin
506
544
  # Add iat to the validation to check if the token has been manipulated
507
- decoded_token = JWT.decode token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }
545
+ decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
508
546
  rescue JWT::InvalidIatError
509
547
  # Handle invalid token, e.g. logout user or deny access
510
548
  end
@@ -514,17 +552,17 @@ end
514
552
 
515
553
  From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
516
554
 
517
- > 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.
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.
518
556
 
519
557
  ```ruby
520
558
  sub = 'Subject'
521
559
  sub_payload = { data: 'data', sub: sub }
522
560
 
523
- token = JWT.encode sub_payload, hmac_secret, 'HS256'
561
+ token = JWT.encode(sub_payload, hmac_secret, 'HS256')
524
562
 
525
563
  begin
526
564
  # Add sub to the validation to check if the token has been manipulated
527
- decoded_token = JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }
565
+ decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
528
566
  rescue JWT::InvalidSubError
529
567
  # Handle invalid token, e.g. logout user or deny access
530
568
  end
@@ -535,6 +573,7 @@ end
535
573
  The JWT claim verifications can be used to verify any Hash to include expected keys and values.
536
574
 
537
575
  A few example on verifying the claims for a payload:
576
+
538
577
  ```ruby
539
578
  JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
540
579
  JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
@@ -546,8 +585,6 @@ JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
546
585
  JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
547
586
  ```
548
587
 
549
-
550
-
551
588
  ### Finding a Key
552
589
 
553
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.
@@ -558,7 +595,7 @@ iss_payload = { data: 'data', iss: issuers.first }
558
595
 
559
596
  secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
560
597
 
561
- token = JWT.encode iss_payload, hmac_secret, 'HS256'
598
+ token = JWT.encode(iss_payload, hmac_secret, 'HS256')
562
599
 
563
600
  begin
564
601
  # Add iss to the validation to check if the token has been manipulated
@@ -573,9 +610,10 @@ end
573
610
  ### Required Claims
574
611
 
575
612
  You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
613
+
576
614
  ```ruby
577
615
  # Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
578
- JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
616
+ JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
579
617
  ```
580
618
 
581
619
  ### X.509 certificates in x5c header
@@ -599,7 +637,7 @@ rescue JWT::DecodeError
599
637
  end
600
638
  ```
601
639
 
602
- ### JSON Web Key (JWK)
640
+ ## JSON Web Key (JWK)
603
641
 
604
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.
605
643
 
@@ -626,14 +664,14 @@ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
626
664
  JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
627
665
  ```
628
666
 
629
-
630
- 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.
631
668
  This can be used to implement caching of remotely fetched JWK Sets.
632
669
 
633
- If the requested `kid` is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
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`.
634
672
  The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
635
673
 
636
- Tokens without a specified `kid` are rejected by default.
674
+ Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default.
637
675
  This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
638
676
 
639
677
  ```ruby
@@ -712,7 +750,7 @@ jwk_hash = jwk.export
712
750
  thumbprint_as_the_kid = jwk_hash[:kid]
713
751
  ```
714
752
 
715
- # Development and testing
753
+ ## Development and testing
716
754
 
717
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.
718
756
 
@@ -721,7 +759,7 @@ bundle install
721
759
  bundle exec appraisal rake test
722
760
  ```
723
761
 
724
- # Releasing
762
+ ## Releasing
725
763
 
726
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:
727
765
 
@@ -732,6 +770,7 @@ rake release:source_control_push
732
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.
733
771
 
734
772
  ## How to contribute
773
+
735
774
  See [CONTRIBUTING](CONTRIBUTING.md).
736
775
 
737
776
  ## Contributors