jwt 2.2.1 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/AUTHORS +79 -44
- data/CHANGELOG.md +271 -20
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/README.md +253 -35
- data/lib/jwt/algos/algo_wrapper.rb +26 -0
- data/lib/jwt/algos/ecdsa.rb +55 -14
- data/lib/jwt/algos/eddsa.rb +18 -8
- data/lib/jwt/algos/hmac.rb +57 -17
- data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
- data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
- data/lib/jwt/algos/none.rb +19 -0
- data/lib/jwt/algos/ps.rb +10 -12
- data/lib/jwt/algos/rsa.rb +9 -5
- data/lib/jwt/algos/unsupported.rb +7 -4
- data/lib/jwt/algos.rb +66 -0
- data/lib/jwt/claims_validator.rb +12 -8
- data/lib/jwt/configuration/container.rb +21 -0
- data/lib/jwt/configuration/decode_configuration.rb +46 -0
- data/lib/jwt/configuration/jwk_configuration.rb +27 -0
- data/lib/jwt/configuration.rb +15 -0
- data/lib/jwt/decode.rb +85 -17
- data/lib/jwt/encode.rb +30 -19
- data/lib/jwt/error.rb +16 -14
- data/lib/jwt/jwk/ec.rb +236 -0
- data/lib/jwt/jwk/hmac.rb +103 -0
- data/lib/jwt/jwk/key_base.rb +55 -0
- data/lib/jwt/jwk/key_finder.rb +19 -30
- data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
- data/lib/jwt/jwk/rsa.rb +181 -25
- data/lib/jwt/jwk/set.rb +80 -0
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +39 -15
- data/lib/jwt/verify.rb +18 -3
- data/lib/jwt/version.rb +23 -3
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +5 -4
- data/ruby-jwt.gemspec +15 -10
- metadata +30 -90
- data/.codeclimate.yml +0 -20
- data/.ebert.yml +0 -18
- data/.gitignore +0 -11
- data/.rspec +0 -1
- data/.rubocop.yml +0 -98
- data/.travis.yml +0 -20
- data/Appraisals +0 -14
- data/Gemfile +0 -3
- data/Rakefile +0 -11
- data/lib/jwt/default_options.rb +0 -15
- data/lib/jwt/security_utils.rb +0 -57
- data/lib/jwt/signature.rb +0 -52
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at antmanj@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Contributing to [ruby-jwt](https://github.com/jwt/ruby-jwt)
|
2
|
+
|
3
|
+
## Forking the project
|
4
|
+
|
5
|
+
Fork the project on GitHub and clone your own fork. Instuctions on forking can be found from the [GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
6
|
+
|
7
|
+
```
|
8
|
+
git clone git@github.com:you/ruby-jwt.git
|
9
|
+
cd ruby-jwt
|
10
|
+
git remote add upstream https://github.com/jwt/ruby-jwt
|
11
|
+
```
|
12
|
+
|
13
|
+
## Create a branch for your implementation
|
14
|
+
|
15
|
+
Make sure you have the latest upstream main branch of the project.
|
16
|
+
|
17
|
+
```
|
18
|
+
git fetch --all
|
19
|
+
git checkout main
|
20
|
+
git rebase upstream/main
|
21
|
+
git push origin main
|
22
|
+
git checkout -b fix-a-little-problem
|
23
|
+
```
|
24
|
+
|
25
|
+
## Running the tests and linter
|
26
|
+
|
27
|
+
Before you start with your implementation make sure you are able to get a successful test run with the current revision.
|
28
|
+
|
29
|
+
The tests are written with rspec and [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
30
|
+
|
31
|
+
[Rubocop](https://github.com/rubocop/rubocop) is used to enforce the Ruby style.
|
32
|
+
|
33
|
+
To run the complete set of tests and linter run the following
|
34
|
+
|
35
|
+
```bash
|
36
|
+
bundle install
|
37
|
+
bundle exec appraisal rake test
|
38
|
+
bundle exec rubocop
|
39
|
+
```
|
40
|
+
|
41
|
+
## Implement your feature
|
42
|
+
|
43
|
+
Implement tests and your change. Don't be shy adding a little something in the [README](README.md).
|
44
|
+
Add a short description of the change in either the `Features` or `Fixes` section in the [CHANGELOG](CHANGELOG.md) file.
|
45
|
+
|
46
|
+
The form of the row (You need to return to the row when you know the pull request id)
|
47
|
+
```
|
48
|
+
- Fix a little problem [#123](https://github.com/jwt/ruby-jwt/pull/123) - [@you](https://github.com/you).
|
49
|
+
```
|
50
|
+
|
51
|
+
## Push your branch and create a pull request
|
52
|
+
|
53
|
+
Before pushing make sure the tests pass and RuboCop is happy.
|
54
|
+
|
55
|
+
```
|
56
|
+
bundle exec appraisal rake test
|
57
|
+
bundle exec rubocop
|
58
|
+
git push origin fix-a-little-problem
|
59
|
+
```
|
60
|
+
|
61
|
+
Make a new pull request on the [ruby-jwt project](https://github.com/jwt/ruby-jwt/pulls) with a description what the change is about.
|
62
|
+
|
63
|
+
## Update the CHANGELOG, again
|
64
|
+
|
65
|
+
Update the [CHANGELOG](CHANGELOG.md) with the pull request id from the previous step.
|
66
|
+
|
67
|
+
You can ammend the previous commit with the updated changelog change and force push your branch. The PR will get automatically updated.
|
68
|
+
|
69
|
+
```
|
70
|
+
git add CHANGELOG.md
|
71
|
+
git commit --amend --no-edit
|
72
|
+
git push origin fix-a-little-problem -f
|
73
|
+
```
|
74
|
+
|
75
|
+
## Keep an eye on your pull request
|
76
|
+
|
77
|
+
A maintainer will review and probably merge you changes when time allows, be patient.
|
78
|
+
|
79
|
+
## Keeping your branch up-to-date
|
80
|
+
|
81
|
+
It's recommended that you keep your branch up-to-date by rebasing to the upstream main.
|
82
|
+
|
83
|
+
```
|
84
|
+
git fetch upstream
|
85
|
+
git checkout fix-a-little-problem
|
86
|
+
git rebase upstream/main
|
87
|
+
git push origin fix-a-little-problem -f
|
88
|
+
```
|
89
|
+
|
90
|
+
# Releasing a new version
|
91
|
+
|
92
|
+
The version is using the [Semantic Versioning](http://semver.org/) and the version is located in the [version.rb](lib/jwt/version.rb) file.
|
93
|
+
Also update the [CHANGELOG](CHANGELOG.md) to reflect the upcoming version release.
|
94
|
+
|
95
|
+
```bash
|
96
|
+
rake release
|
97
|
+
```
|
98
|
+
|
99
|
+
**If you want a release cut with your PR, please include a version bump according to **
|
data/README.md
CHANGED
@@ -1,26 +1,33 @@
|
|
1
1
|
# JWT
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
|
4
|
-
[![Build Status](https://
|
4
|
+
[![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
|
6
6
|
[![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
|
7
7
|
[![Issue Count](https://codeclimate.com/github/jwt/ruby-jwt/badges/issue_count.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
|
8
|
-
[![Ebert](https://ebertapp.io/github/jwt/ruby-jwt.svg)](https://ebertapp.io/github/jwt/ruby-jwt)
|
9
8
|
|
10
9
|
A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
|
11
10
|
|
12
11
|
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
13
12
|
|
14
13
|
## Announcements
|
15
|
-
|
14
|
+
* Ruby 2.4 support was dropped in version 2.4.0
|
16
15
|
* Ruby 1.9.3 support was dropped at December 31st, 2016.
|
17
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)
|
18
17
|
|
18
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
19
|
+
|
20
|
+
## Sponsors
|
21
|
+
|
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)|
|
25
|
+
|
19
26
|
## Installing
|
20
27
|
|
21
28
|
### Using Rubygems:
|
22
29
|
```bash
|
23
|
-
|
30
|
+
gem install jwt
|
24
31
|
```
|
25
32
|
|
26
33
|
### Using Bundler:
|
@@ -32,11 +39,11 @@ And run `bundle install`
|
|
32
39
|
|
33
40
|
## Algorithms and Usage
|
34
41
|
|
35
|
-
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/
|
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**
|
36
43
|
|
37
44
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
38
45
|
|
39
|
-
**NONE**
|
46
|
+
### **NONE**
|
40
47
|
|
41
48
|
* none - unsigned token
|
42
49
|
|
@@ -62,7 +69,7 @@ decoded_token = JWT.decode token, nil, false
|
|
62
69
|
puts decoded_token
|
63
70
|
```
|
64
71
|
|
65
|
-
**HMAC**
|
72
|
+
### **HMAC**
|
66
73
|
|
67
74
|
* HS256 - HMAC using SHA-256 hash algorithm
|
68
75
|
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
@@ -70,6 +77,7 @@ puts decoded_token
|
|
70
77
|
* HS512 - HMAC using SHA-512 hash algorithm
|
71
78
|
|
72
79
|
```ruby
|
80
|
+
# 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.
|
73
81
|
hmac_secret = 'my$ecretK3y'
|
74
82
|
|
75
83
|
token = JWT.encode payload, hmac_secret, 'HS256'
|
@@ -87,13 +95,13 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
|
87
95
|
puts decoded_token
|
88
96
|
```
|
89
97
|
|
90
|
-
Note: If [RbNaCl](https://github.com/
|
98
|
+
Note: If [RbNaCl](https://github.com/RubyCrypto/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl prior to 6.0.0 only support a maximum key size of 32 bytes for these algorithms.
|
91
99
|
|
92
|
-
[RbNaCl](https://github.com/
|
100
|
+
[RbNaCl](https://github.com/RubyCrypto/rbnacl) requires
|
93
101
|
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
94
102
|
on MacOS with `brew install libsodium`.
|
95
103
|
|
96
|
-
**RSA**
|
104
|
+
### **RSA**
|
97
105
|
|
98
106
|
* RS256 - RSA using SHA-256 hash algorithm
|
99
107
|
* RS384 - RSA using SHA-384 hash algorithm
|
@@ -118,24 +126,22 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
|
|
118
126
|
puts decoded_token
|
119
127
|
```
|
120
128
|
|
121
|
-
**ECDSA**
|
129
|
+
### **ECDSA**
|
122
130
|
|
123
131
|
* ES256 - ECDSA using P-256 and SHA-256
|
124
132
|
* ES384 - ECDSA using P-384 and SHA-384
|
125
133
|
* ES512 - ECDSA using P-521 and SHA-512
|
134
|
+
* ES256K - ECDSA using P-256K and SHA-256
|
126
135
|
|
127
136
|
```ruby
|
128
|
-
ecdsa_key = OpenSSL::PKey::EC.
|
129
|
-
ecdsa_key.generate_key
|
130
|
-
ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key
|
131
|
-
ecdsa_public.private_key = nil
|
137
|
+
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
132
138
|
|
133
139
|
token = JWT.encode payload, ecdsa_key, 'ES256'
|
134
140
|
|
135
141
|
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
136
142
|
puts token
|
137
143
|
|
138
|
-
decoded_token = JWT.decode token,
|
144
|
+
decoded_token = JWT.decode token, ecdsa_key, true, { algorithm: 'ES256' }
|
139
145
|
|
140
146
|
# Array
|
141
147
|
# [
|
@@ -145,7 +151,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
|
|
145
151
|
puts decoded_token
|
146
152
|
```
|
147
153
|
|
148
|
-
**EDDSA**
|
154
|
+
### **EDDSA**
|
149
155
|
|
150
156
|
In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
|
151
157
|
|
@@ -153,7 +159,7 @@ In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`
|
|
153
159
|
gem 'rbnacl'
|
154
160
|
```
|
155
161
|
|
156
|
-
For more detailed installation instruction check the official [repository](https://github.com/
|
162
|
+
For more detailed installation instruction check the official [repository](https://github.com/RubyCrypto/rbnacl) on GitHub.
|
157
163
|
|
158
164
|
* ED25519
|
159
165
|
|
@@ -174,9 +180,9 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
|
174
180
|
|
175
181
|
```
|
176
182
|
|
177
|
-
**RSASSA-PSS**
|
183
|
+
### **RSASSA-PSS**
|
178
184
|
|
179
|
-
In order to use this algorithm you need to add the `openssl` gem to
|
185
|
+
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`.
|
180
186
|
|
181
187
|
```ruby
|
182
188
|
gem 'openssl', '~> 2.1'
|
@@ -205,6 +211,33 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
|
|
205
211
|
puts decoded_token
|
206
212
|
```
|
207
213
|
|
214
|
+
### **Custom algorithms**
|
215
|
+
|
216
|
+
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.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
module CustomHS512Algorithm
|
220
|
+
def self.alg
|
221
|
+
'HS512'
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.valid_alg?(alg_to_validate)
|
225
|
+
alg_to_validate == alg
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.sign(data:, signing_key:)
|
229
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key)
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.verify(data:, signature:, verification_key:)
|
233
|
+
::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
|
238
|
+
payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
|
239
|
+
```
|
240
|
+
|
208
241
|
## Support for reserved claim names
|
209
242
|
JSON Web Token defines some reserved claim names and defines how they should be
|
210
243
|
used. JWT supports these reserved claim names:
|
@@ -270,6 +303,12 @@ rescue JWT::ExpiredSignature
|
|
270
303
|
end
|
271
304
|
```
|
272
305
|
|
306
|
+
The Expiration Claim verification can be disabled.
|
307
|
+
```ruby
|
308
|
+
# Decode token without raising JWT::ExpiredSignature error
|
309
|
+
JWT.decode token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }
|
310
|
+
```
|
311
|
+
|
273
312
|
**Adding Leeway**
|
274
313
|
|
275
314
|
```ruby
|
@@ -310,6 +349,12 @@ rescue JWT::ImmatureSignature
|
|
310
349
|
end
|
311
350
|
```
|
312
351
|
|
352
|
+
The Not Before Claim verification can be disabled.
|
353
|
+
```ruby
|
354
|
+
# Decode token without raising JWT::ImmatureSignature error
|
355
|
+
JWT.decode token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }
|
356
|
+
```
|
357
|
+
|
313
358
|
**Adding Leeway**
|
314
359
|
|
315
360
|
```ruby
|
@@ -351,6 +396,36 @@ rescue JWT::InvalidIssuerError
|
|
351
396
|
end
|
352
397
|
```
|
353
398
|
|
399
|
+
You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy.
|
400
|
+
On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have
|
401
|
+
to convert them to proc (using `to_proc`)
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
JWT.decode token, hmac_secret, true,
|
405
|
+
iss: %r'https://my.awesome.website/',
|
406
|
+
verify_iss: true,
|
407
|
+
algorithm: 'HS256'
|
408
|
+
```
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
JWT.decode token, hmac_secret, true,
|
412
|
+
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
413
|
+
verify_iss: true,
|
414
|
+
algorithm: 'HS256'
|
415
|
+
```
|
416
|
+
|
417
|
+
```ruby
|
418
|
+
JWT.decode token, hmac_secret, true,
|
419
|
+
iss: method(:valid_issuer?),
|
420
|
+
verify_iss: true,
|
421
|
+
algorithm: 'HS256'
|
422
|
+
|
423
|
+
# somewhere in the same class:
|
424
|
+
def valid_issuer?(issuer)
|
425
|
+
# custom validation
|
426
|
+
end
|
427
|
+
```
|
428
|
+
|
354
429
|
### Audience Claim
|
355
430
|
|
356
431
|
From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
|
@@ -391,6 +466,8 @@ begin
|
|
391
466
|
#decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }
|
392
467
|
# Alternatively, pass a proc with your own code to check if the JTI has already been used
|
393
468
|
decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }
|
469
|
+
# or
|
470
|
+
decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }
|
394
471
|
rescue JWT::InvalidJtiError
|
395
472
|
# Handle invalid token, e.g. logout user or deny access
|
396
473
|
puts 'Error'
|
@@ -439,31 +516,170 @@ rescue JWT::InvalidSubError
|
|
439
516
|
end
|
440
517
|
```
|
441
518
|
|
519
|
+
### Finding a Key
|
520
|
+
|
521
|
+
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.
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
issuers = %w[My_Awesome_Company1 My_Awesome_Company2]
|
525
|
+
iss_payload = { data: 'data', iss: issuers.first }
|
526
|
+
|
527
|
+
secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
|
528
|
+
|
529
|
+
token = JWT.encode iss_payload, hmac_secret, 'HS256'
|
530
|
+
|
531
|
+
begin
|
532
|
+
# Add iss to the validation to check if the token has been manipulated
|
533
|
+
decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload|
|
534
|
+
secrets[payload['iss']]
|
535
|
+
end
|
536
|
+
rescue JWT::InvalidIssuerError
|
537
|
+
# Handle invalid token, e.g. logout user or deny access
|
538
|
+
end
|
539
|
+
```
|
540
|
+
|
541
|
+
### Required Claims
|
542
|
+
|
543
|
+
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
544
|
+
```ruby
|
545
|
+
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
546
|
+
JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
|
547
|
+
```
|
548
|
+
|
549
|
+
### X.509 certificates in x5c header
|
550
|
+
|
551
|
+
A JWT signature can be verified using certificate(s) given in the `x5c` header. Before doing that, the trustworthiness of these certificate(s) must be established. This is done in accordance with RFC 5280 which (among other things) verifies the certificate(s) are issued by a trusted root certificate, the timestamps are valid, and none of the certificate(s) are revoked (i.e. being present in the root certificate's Certificate Revocation List).
|
552
|
+
|
553
|
+
```ruby
|
554
|
+
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
|
555
|
+
crl_uris = root_certificates.map(&:crl_uris)
|
556
|
+
crls = crl_uris.map do |uri|
|
557
|
+
# look up cached CRL by `uri` and return it if found, otherwise continue
|
558
|
+
crl = Net::HTTP.get(uri)
|
559
|
+
crl = OpenSSL::X509::CRL.new(crl)
|
560
|
+
# cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
|
561
|
+
end
|
562
|
+
|
563
|
+
begin
|
564
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
|
565
|
+
rescue JWT::DecodeError
|
566
|
+
# Handle error, e.g. x5c header certificate revoked or expired
|
567
|
+
end
|
568
|
+
```
|
569
|
+
|
442
570
|
### JSON Web Key (JWK)
|
443
571
|
|
444
|
-
JWK is a JSON structure representing a cryptographic key.
|
572
|
+
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.
|
573
|
+
|
574
|
+
To encode a JWT using your JWK:
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
|
578
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
|
579
|
+
|
580
|
+
# Encoding
|
581
|
+
payload = { data: 'data' }
|
582
|
+
token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
|
583
|
+
|
584
|
+
# JSON Web Key Set for advertising your signing keys
|
585
|
+
jwks_hash = JWT::JWK::Set.new(jwk).export
|
586
|
+
```
|
587
|
+
|
588
|
+
To decode a JWT using a trusted entity's JSON Web Key Set (JWKS):
|
445
589
|
|
446
590
|
```ruby
|
447
|
-
|
448
|
-
|
591
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
592
|
+
jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
|
593
|
+
algorithms = jwks.map { |key| key[:alg] }.compact.uniq
|
594
|
+
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
|
595
|
+
```
|
596
|
+
|
597
|
+
|
598
|
+
The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
|
599
|
+
This can be used to implement caching of remotely fetched JWK Sets.
|
449
600
|
|
450
|
-
|
601
|
+
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`.
|
602
|
+
The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
|
451
603
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
604
|
+
Tokens without a specified `kid` are rejected by default.
|
605
|
+
This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
jwks_loader = ->(options) do
|
609
|
+
# The jwk loader would fetch the set of JWKs from a trusted source.
|
610
|
+
# To avoid malicious requests triggering cache invalidations there needs to be
|
611
|
+
# some kind of grace time or other logic for determining the validity of the invalidation.
|
612
|
+
# This example only allows cache invalidations every 5 minutes.
|
613
|
+
if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
|
614
|
+
logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
|
615
|
+
@cached_keys = nil
|
616
|
+
end
|
617
|
+
@cached_keys ||= begin
|
618
|
+
@cache_last_update = Time.now.to_i
|
619
|
+
# Replace with your own JWKS fetching routine
|
620
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
621
|
+
jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
|
622
|
+
jwks
|
623
|
+
end
|
456
624
|
end
|
457
625
|
|
458
626
|
begin
|
459
|
-
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks:
|
627
|
+
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
|
460
628
|
rescue JWT::JWKError
|
461
629
|
# Handle problems with the provided JWKs
|
462
630
|
rescue JWT::DecodeError
|
463
|
-
# Handle other decode related issues e.g. no kid in header, no matching public key found etc.
|
631
|
+
# Handle other decode related issues e.g. no kid in header, no matching public key found etc.
|
464
632
|
end
|
465
633
|
```
|
466
634
|
|
635
|
+
### Importing and exporting JSON Web Keys
|
636
|
+
|
637
|
+
The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys
|
638
|
+
and export to either format with and without the private key included.
|
639
|
+
|
640
|
+
To include the private key in the export pass the `include_private` parameter to the export method.
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
# Import a JWK Hash (showing an HMAC example)
|
644
|
+
jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
|
645
|
+
|
646
|
+
# Import an OpenSSL key
|
647
|
+
# You can optionally add descriptive parameters to the JWK
|
648
|
+
desc_params = { kid: 'my-kid', use: 'sig' }
|
649
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
|
650
|
+
|
651
|
+
# Export as JWK Hash (public key only by default)
|
652
|
+
jwk_hash = jwk.export
|
653
|
+
jwk_hash_with_private_key = jwk.export(include_private: true)
|
654
|
+
|
655
|
+
# Export as OpenSSL key
|
656
|
+
public_key = jwk.verify_key
|
657
|
+
private_key = jwk.signing_key if jwk.private?
|
658
|
+
|
659
|
+
# You can also import and export entire JSON Web Key Sets
|
660
|
+
jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
|
661
|
+
jwks = JWT::JWK::Set.new(jwks_hash)
|
662
|
+
jwks_hash = jwks.export
|
663
|
+
```
|
664
|
+
|
665
|
+
### Key ID (kid) and JWKs
|
666
|
+
|
667
|
+
The key id (kid) generation in the gem is a custom algorithm and not based on any standards.
|
668
|
+
To use a standardized JWK thumbprint (RFC 7638) as the kid for JWKs a generator type can be specified in the global configuration
|
669
|
+
or can be given to the JWK instance on initialization.
|
670
|
+
|
671
|
+
```ruby
|
672
|
+
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
|
673
|
+
# OR
|
674
|
+
JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
|
675
|
+
# OR
|
676
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
|
677
|
+
|
678
|
+
jwk_hash = jwk.export
|
679
|
+
|
680
|
+
thumbprint_as_the_kid = jwk_hash[:kid]
|
681
|
+
```
|
682
|
+
|
467
683
|
# Development and Tests
|
468
684
|
|
469
685
|
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
|
@@ -472,18 +688,20 @@ We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec an
|
|
472
688
|
rake release
|
473
689
|
```
|
474
690
|
|
475
|
-
The tests are written with rspec.
|
691
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
476
692
|
|
477
693
|
```bash
|
478
|
-
bundle
|
694
|
+
bundle install
|
695
|
+
bundle exec appraisal rake test
|
479
696
|
```
|
480
697
|
|
481
|
-
|
698
|
+
## How to contribute
|
699
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
482
700
|
|
483
701
|
## Contributors
|
484
702
|
|
485
|
-
See
|
703
|
+
See [AUTHORS](AUTHORS).
|
486
704
|
|
487
705
|
## License
|
488
706
|
|
489
|
-
See
|
707
|
+
See [LICENSE](LICENSE).
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Algos
|
5
|
+
class AlgoWrapper
|
6
|
+
attr_reader :alg, :cls
|
7
|
+
|
8
|
+
def initialize(alg, cls)
|
9
|
+
@alg = alg
|
10
|
+
@cls = cls
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_alg?(alg_to_check)
|
14
|
+
alg&.casecmp(alg_to_check)&.zero? == true
|
15
|
+
end
|
16
|
+
|
17
|
+
def sign(data:, signing_key:)
|
18
|
+
cls.sign(alg, data, signing_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify(data:, signature:, verification_key:)
|
22
|
+
cls.verify(alg, verification_key, data, signature)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|