jwt 2.8.2 → 3.1.1

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