jwt 2.9.3 → 2.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -16
- data/README.md +153 -83
- data/lib/jwt/base64.rb +3 -0
- data/lib/jwt/claims/audience.rb +10 -0
- data/lib/jwt/claims/crit.rb +35 -0
- data/lib/jwt/claims/decode_verifier.rb +3 -3
- data/lib/jwt/claims/expiration.rb +10 -0
- data/lib/jwt/claims/issued_at.rb +7 -0
- data/lib/jwt/claims/issuer.rb +10 -0
- data/lib/jwt/claims/jwt_id.rb +10 -0
- data/lib/jwt/claims/not_before.rb +10 -0
- data/lib/jwt/claims/numeric.rb +22 -0
- data/lib/jwt/claims/required.rb +10 -0
- data/lib/jwt/claims/subject.rb +10 -0
- data/lib/jwt/claims/verification_methods.rb +20 -0
- data/lib/jwt/claims/verifier.rb +6 -7
- data/lib/jwt/claims.rb +6 -14
- data/lib/jwt/claims_validator.rb +4 -2
- data/lib/jwt/configuration/container.rb +20 -0
- data/lib/jwt/configuration/decode_configuration.rb +24 -0
- data/lib/jwt/configuration/jwk_configuration.rb +1 -0
- data/lib/jwt/configuration.rb +8 -0
- data/lib/jwt/decode.rb +28 -68
- data/lib/jwt/deprecations.rb +1 -0
- data/lib/jwt/encode.rb +17 -56
- data/lib/jwt/encoded_token.rb +139 -0
- data/lib/jwt/error.rb +34 -0
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/compat.rb +3 -0
- data/lib/jwt/jwa/ecdsa.rb +3 -6
- data/lib/jwt/jwa/eddsa.rb +7 -6
- data/lib/jwt/jwa/hmac.rb +2 -3
- data/lib/jwt/jwa/hmac_rbnacl.rb +1 -0
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +1 -0
- data/lib/jwt/jwa/none.rb +1 -0
- data/lib/jwt/jwa/ps.rb +2 -3
- data/lib/jwt/jwa/rsa.rb +2 -3
- data/lib/jwt/jwa/signing_algorithm.rb +3 -0
- data/lib/jwt/jwa/unsupported.rb +1 -0
- data/lib/jwt/jwa/wrapper.rb +1 -0
- data/lib/jwt/jwa.rb +11 -3
- data/lib/jwt/jwk/ec.rb +2 -3
- data/lib/jwt/jwk/hmac.rb +2 -3
- data/lib/jwt/jwk/key_base.rb +1 -0
- data/lib/jwt/jwk/key_finder.rb +1 -0
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
- data/lib/jwt/jwk/rsa.rb +2 -3
- data/lib/jwt/jwk/set.rb +2 -0
- data/lib/jwt/jwk.rb +1 -0
- data/lib/jwt/token.rb +112 -0
- data/lib/jwt/verify.rb +6 -0
- data/lib/jwt/version.rb +33 -10
- data/lib/jwt.rb +16 -0
- metadata +8 -4
@@ -4,12 +4,12 @@ module JWT
|
|
4
4
|
module Claims
|
5
5
|
# Context class to contain the data passed to individual claim validators
|
6
6
|
#
|
7
|
-
# @private
|
7
|
+
# @api private
|
8
8
|
VerificationContext = Struct.new(:payload, keyword_init: true)
|
9
9
|
|
10
10
|
# Verifiers to support the ::JWT.decode method
|
11
11
|
#
|
12
|
-
# @private
|
12
|
+
# @api private
|
13
13
|
module DecodeVerifier
|
14
14
|
VERIFIERS = {
|
15
15
|
verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) },
|
@@ -25,7 +25,7 @@ module JWT
|
|
25
25
|
private_constant(:VERIFIERS)
|
26
26
|
|
27
27
|
class << self
|
28
|
-
# @private
|
28
|
+
# @api private
|
29
29
|
def verify!(payload, options)
|
30
30
|
VERIFIERS.each do |key, verifier_builder|
|
31
31
|
next unless options[key] || options[key.to_s]
|
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The Expiration class is responsible for validating the expiration claim ('exp') in a JWT token.
|
5
6
|
class Expiration
|
7
|
+
# Initializes a new Expiration instance.
|
8
|
+
#
|
9
|
+
# @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the expiration time. Default: 0.
|
6
10
|
def initialize(leeway:)
|
7
11
|
@leeway = leeway || 0
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies the expiration claim ('exp') in the JWT token.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::ExpiredSignature] if the token has expired.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
return unless context.payload.is_a?(Hash)
|
12
22
|
return unless context.payload.key?('exp')
|
data/lib/jwt/claims/issued_at.rb
CHANGED
@@ -2,7 +2,14 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The IssuedAt class is responsible for validating the issued at claim ('iat') in a JWT token.
|
5
6
|
class IssuedAt
|
7
|
+
# Verifies the issued at claim ('iat') in the JWT token.
|
8
|
+
#
|
9
|
+
# @param context [Object] the context containing the JWT payload.
|
10
|
+
# @param _args [Hash] additional arguments (not used).
|
11
|
+
# @raise [JWT::InvalidIatError] if the issued at claim is invalid.
|
12
|
+
# @return [nil]
|
6
13
|
def verify!(context:, **_args)
|
7
14
|
return unless context.payload.is_a?(Hash)
|
8
15
|
return unless context.payload.key?('iat')
|
data/lib/jwt/claims/issuer.rb
CHANGED
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The Issuer class is responsible for validating the issuer claim ('iss') in a JWT token.
|
5
6
|
class Issuer
|
7
|
+
# Initializes a new Issuer instance.
|
8
|
+
#
|
9
|
+
# @param issuers [String, Symbol, Array<String, Symbol>] the expected issuer(s) for the JWT token.
|
6
10
|
def initialize(issuers:)
|
7
11
|
@issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item }
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies the issuer claim ('iss') in the JWT token.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::InvalidIssuerError] if the issuer claim is invalid.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
case (iss = context.payload['iss'])
|
12
22
|
when *issuers
|
data/lib/jwt/claims/jwt_id.rb
CHANGED
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The JwtId class is responsible for validating the JWT ID claim ('jti') in a JWT token.
|
5
6
|
class JwtId
|
7
|
+
# Initializes a new JwtId instance.
|
8
|
+
#
|
9
|
+
# @param validator [#call] an object responding to `call` to validate the JWT ID.
|
6
10
|
def initialize(validator:)
|
7
11
|
@validator = validator
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies the JWT ID claim ('jti') in the JWT token.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::InvalidJtiError] if the JWT ID claim is invalid or missing.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
jti = context.payload['jti']
|
12
22
|
if validator.respond_to?(:call)
|
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The NotBefore class is responsible for validating the 'nbf' (Not Before) claim in a JWT token.
|
5
6
|
class NotBefore
|
7
|
+
# Initializes a new NotBefore instance.
|
8
|
+
#
|
9
|
+
# @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the 'nbf' claim. Defaults to 0.
|
6
10
|
def initialize(leeway:)
|
7
11
|
@leeway = leeway || 0
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies the 'nbf' (Not Before) claim in the JWT token.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::ImmatureSignature] if the 'nbf' claim has not been reached.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
return unless context.payload.is_a?(Hash)
|
12
22
|
return unless context.payload.key?('nbf')
|
data/lib/jwt/claims/numeric.rb
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The Numeric class is responsible for validating numeric claims in a JWT token.
|
6
|
+
# The numeric claims are: exp, iat and nbf
|
5
7
|
class Numeric
|
8
|
+
# The Compat class provides backward compatibility for numeric claim validation.
|
9
|
+
# @api private
|
6
10
|
class Compat
|
7
11
|
def initialize(payload)
|
8
12
|
@payload = payload
|
@@ -13,23 +17,41 @@ module JWT
|
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
20
|
+
# List of numeric claims that can be validated.
|
16
21
|
NUMERIC_CLAIMS = %i[
|
17
22
|
exp
|
18
23
|
iat
|
19
24
|
nbf
|
20
25
|
].freeze
|
21
26
|
|
27
|
+
private_constant(:NUMERIC_CLAIMS)
|
28
|
+
|
29
|
+
# @api private
|
22
30
|
def self.new(*args)
|
23
31
|
return super if args.empty?
|
24
32
|
|
33
|
+
Deprecations.warning('Calling ::JWT::Claims::Numeric.new with the payload will be removed in the next major version of ruby-jwt')
|
25
34
|
Compat.new(*args)
|
26
35
|
end
|
27
36
|
|
37
|
+
# Verifies the numeric claims in the JWT context.
|
38
|
+
#
|
39
|
+
# @param context [Object] the context containing the JWT payload.
|
40
|
+
# @raise [JWT::InvalidClaimError] if any numeric claim is invalid.
|
41
|
+
# @return [nil]
|
28
42
|
def verify!(context:)
|
29
43
|
validate_numeric_claims(context.payload)
|
30
44
|
end
|
31
45
|
|
46
|
+
# Verifies the numeric claims in the JWT payload.
|
47
|
+
#
|
48
|
+
# @param payload [Hash] the JWT payload containing the claims.
|
49
|
+
# @param _args [Hash] additional arguments (not used).
|
50
|
+
# @raise [JWT::InvalidClaimError] if any numeric claim is invalid.
|
51
|
+
# @return [nil]
|
52
|
+
# @deprecated The ::JWT::Claims::Numeric.verify! method will be removed in the next major version of ruby-jwt
|
32
53
|
def self.verify!(payload:, **_args)
|
54
|
+
Deprecations.warning('The ::JWT::Claims::Numeric.verify! method will be removed in the next major version of ruby-jwt.')
|
33
55
|
JWT::Claims.verify_payload!(payload, :numeric)
|
34
56
|
end
|
35
57
|
|
data/lib/jwt/claims/required.rb
CHANGED
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The Required class is responsible for validating that all required claims are present in a JWT token.
|
5
6
|
class Required
|
7
|
+
# Initializes a new Required instance.
|
8
|
+
#
|
9
|
+
# @param required_claims [Array<String>] the list of required claims.
|
6
10
|
def initialize(required_claims:)
|
7
11
|
@required_claims = required_claims
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies that all required claims are present in the JWT payload.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::MissingRequiredClaim] if any required claim is missing.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
required_claims.each do |required_claim|
|
12
22
|
next if context.payload.is_a?(Hash) && context.payload.key?(required_claim)
|
data/lib/jwt/claims/subject.rb
CHANGED
@@ -2,11 +2,21 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
+
# The Subject class is responsible for validating the subject claim ('sub') in a JWT token.
|
5
6
|
class Subject
|
7
|
+
# Initializes a new Subject instance.
|
8
|
+
#
|
9
|
+
# @param expected_subject [String] the expected subject for the JWT token.
|
6
10
|
def initialize(expected_subject:)
|
7
11
|
@expected_subject = expected_subject.to_s
|
8
12
|
end
|
9
13
|
|
14
|
+
# Verifies the subject claim ('sub') in the JWT token.
|
15
|
+
#
|
16
|
+
# @param context [Object] the context containing the JWT payload.
|
17
|
+
# @param _args [Hash] additional arguments (not used).
|
18
|
+
# @raise [JWT::InvalidSubError] if the subject claim is invalid.
|
19
|
+
# @return [nil]
|
10
20
|
def verify!(context:, **_args)
|
11
21
|
sub = context.payload['sub']
|
12
22
|
raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || '<none>'}") unless sub.to_s == expected_subject
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
# @api private
|
6
|
+
module VerificationMethods
|
7
|
+
def verify_claims!(*options)
|
8
|
+
Verifier.verify!(self, *options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def claim_errors(*options)
|
12
|
+
Verifier.errors(self, *options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid_claims?(*options)
|
16
|
+
claim_errors(*options).empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/jwt/claims/verifier.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Claims
|
5
|
-
# @private
|
5
|
+
# @api private
|
6
6
|
module Verifier
|
7
7
|
VERIFIERS = {
|
8
8
|
exp: ->(options) { Claims::Expiration.new(leeway: options.dig(:exp, :leeway)) },
|
@@ -12,7 +12,7 @@ module JWT
|
|
12
12
|
jti: ->(options) { Claims::JwtId.new(validator: options[:jti]) },
|
13
13
|
aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) },
|
14
14
|
sub: ->(options) { Claims::Subject.new(expected_subject: options[:sub]) },
|
15
|
-
|
15
|
+
crit: ->(options) { Claims::Crit.new(expected_crits: options[:crit]) },
|
16
16
|
required: ->(options) { Claims::Required.new(required_claims: options[:required]) },
|
17
17
|
numeric: ->(*) { Claims::Numeric.new }
|
18
18
|
}.freeze
|
@@ -20,7 +20,7 @@ module JWT
|
|
20
20
|
private_constant(:VERIFIERS)
|
21
21
|
|
22
22
|
class << self
|
23
|
-
# @private
|
23
|
+
# @api private
|
24
24
|
def verify!(context, *options)
|
25
25
|
iterate_verifiers(*options) do |verifier, verifier_options|
|
26
26
|
verify_one!(context, verifier, verifier_options)
|
@@ -28,7 +28,7 @@ module JWT
|
|
28
28
|
nil
|
29
29
|
end
|
30
30
|
|
31
|
-
# @private
|
31
|
+
# @api private
|
32
32
|
def errors(context, *options)
|
33
33
|
errors = []
|
34
34
|
iterate_verifiers(*options) do |verifier, verifier_options|
|
@@ -39,7 +39,8 @@ module JWT
|
|
39
39
|
errors
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
private
|
43
|
+
|
43
44
|
def iterate_verifiers(*options)
|
44
45
|
options.each do |element|
|
45
46
|
if element.is_a?(Hash)
|
@@ -50,8 +51,6 @@ module JWT
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
private
|
54
|
-
|
55
54
|
def verify_one!(context, verifier, options)
|
56
55
|
verifier_builder = VERIFIERS.fetch(verifier) { raise ArgumentError, "#{verifier} not a valid claim verifier" }
|
57
56
|
verifier_builder.call(options || {}).verify!(context: context)
|
data/lib/jwt/claims.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'claims/audience'
|
4
|
+
require_relative 'claims/crit'
|
5
|
+
require_relative 'claims/decode_verifier'
|
4
6
|
require_relative 'claims/expiration'
|
5
7
|
require_relative 'claims/issued_at'
|
6
8
|
require_relative 'claims/issuer'
|
@@ -9,7 +11,7 @@ require_relative 'claims/not_before'
|
|
9
11
|
require_relative 'claims/numeric'
|
10
12
|
require_relative 'claims/required'
|
11
13
|
require_relative 'claims/subject'
|
12
|
-
require_relative 'claims/
|
14
|
+
require_relative 'claims/verification_methods'
|
13
15
|
require_relative 'claims/verifier'
|
14
16
|
|
15
17
|
module JWT
|
@@ -26,7 +28,6 @@ module JWT
|
|
26
28
|
# sub
|
27
29
|
# required
|
28
30
|
# numeric
|
29
|
-
#
|
30
31
|
module Claims
|
31
32
|
# Represents a claim verification error
|
32
33
|
Error = Struct.new(:message, keyword_init: true)
|
@@ -34,6 +35,7 @@ module JWT
|
|
34
35
|
class << self
|
35
36
|
# @deprecated Use {verify_payload!} instead. Will be removed in the next major version of ruby-jwt.
|
36
37
|
def verify!(payload, options)
|
38
|
+
Deprecations.warning('The ::JWT::Claims.verify! method is deprecated will be removed in the next major version of ruby-jwt')
|
37
39
|
DecodeVerifier.verify!(payload, options)
|
38
40
|
end
|
39
41
|
|
@@ -48,7 +50,7 @@ module JWT
|
|
48
50
|
# @return [void]
|
49
51
|
# @raise [JWT::DecodeError] if any claim is invalid.
|
50
52
|
def verify_payload!(payload, *options)
|
51
|
-
|
53
|
+
Verifier.verify!(VerificationContext.new(payload: payload), *options)
|
52
54
|
end
|
53
55
|
|
54
56
|
# Checks if the claims in the JWT payload are valid.
|
@@ -65,17 +67,7 @@ module JWT
|
|
65
67
|
# @param options [Array] the options for verifying the claims.
|
66
68
|
# @return [Array<JWT::Claims::Error>] the errors in the claims of the JWT
|
67
69
|
def payload_errors(payload, *options)
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
def verify_token!(token, *options)
|
74
|
-
Verifier.verify!(token, *options)
|
75
|
-
end
|
76
|
-
|
77
|
-
def token_errors(token, *options)
|
78
|
-
Verifier.errors(token, *options)
|
70
|
+
Verifier.errors(VerificationContext.new(payload: payload), *options)
|
79
71
|
end
|
80
72
|
end
|
81
73
|
end
|
data/lib/jwt/claims_validator.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'error'
|
4
|
-
|
5
3
|
module JWT
|
4
|
+
# @deprecated Use `Claims.verify_payload!` directly instead.
|
6
5
|
class ClaimsValidator
|
6
|
+
# @deprecated Use `Claims.verify_payload!` directly instead.
|
7
7
|
def initialize(payload)
|
8
|
+
Deprecations.warning('The ::JWT::ClaimsValidator class is deprecated and will be removed in the next major version of ruby-jwt')
|
8
9
|
@payload = payload
|
9
10
|
end
|
10
11
|
|
12
|
+
# @deprecated Use `Claims.verify_payload!` directly instead.
|
11
13
|
def validate!
|
12
14
|
Claims.verify_payload!(@payload, :numeric)
|
13
15
|
true
|
@@ -5,14 +5,28 @@ require_relative 'jwk_configuration'
|
|
5
5
|
|
6
6
|
module JWT
|
7
7
|
module Configuration
|
8
|
+
# The Container class holds the configuration settings for JWT.
|
8
9
|
class Container
|
10
|
+
# @!attribute [rw] decode
|
11
|
+
# @return [DecodeConfiguration] the decode configuration.
|
12
|
+
# @!attribute [rw] jwk
|
13
|
+
# @return [JwkConfiguration] the JWK configuration.
|
14
|
+
# @!attribute [rw] strict_base64_decoding
|
15
|
+
# @return [Boolean] whether strict Base64 decoding is enabled.
|
9
16
|
attr_accessor :decode, :jwk, :strict_base64_decoding
|
17
|
+
|
18
|
+
# @!attribute [r] deprecation_warnings
|
19
|
+
# @return [Symbol] the deprecation warnings setting.
|
10
20
|
attr_reader :deprecation_warnings
|
11
21
|
|
22
|
+
# Initializes a new Container instance and resets the configuration.
|
12
23
|
def initialize
|
13
24
|
reset!
|
14
25
|
end
|
15
26
|
|
27
|
+
# Resets the configuration to default values.
|
28
|
+
#
|
29
|
+
# @return [void]
|
16
30
|
def reset!
|
17
31
|
@decode = DecodeConfiguration.new
|
18
32
|
@jwk = JwkConfiguration.new
|
@@ -22,6 +36,12 @@ module JWT
|
|
22
36
|
end
|
23
37
|
|
24
38
|
DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze
|
39
|
+
private_constant(:DEPRECATION_WARNINGS_VALUES)
|
40
|
+
# Sets the deprecation warnings setting.
|
41
|
+
#
|
42
|
+
# @param value [Symbol] the deprecation warnings setting. Must be one of `:once`, `:warn`, or `:silent`.
|
43
|
+
# @raise [ArgumentError] if the value is not one of the supported values.
|
44
|
+
# @return [void]
|
25
45
|
def deprecation_warnings=(value)
|
26
46
|
raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value)
|
27
47
|
|
@@ -2,7 +2,29 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module Configuration
|
5
|
+
# The DecodeConfiguration class holds the configuration settings for decoding JWT tokens.
|
5
6
|
class DecodeConfiguration
|
7
|
+
# @!attribute [rw] verify_expiration
|
8
|
+
# @return [Boolean] whether to verify the expiration claim.
|
9
|
+
# @!attribute [rw] verify_not_before
|
10
|
+
# @return [Boolean] whether to verify the not before claim.
|
11
|
+
# @!attribute [rw] verify_iss
|
12
|
+
# @return [Boolean] whether to verify the issuer claim.
|
13
|
+
# @!attribute [rw] verify_iat
|
14
|
+
# @return [Boolean] whether to verify the issued at claim.
|
15
|
+
# @!attribute [rw] verify_jti
|
16
|
+
# @return [Boolean] whether to verify the JWT ID claim.
|
17
|
+
# @!attribute [rw] verify_aud
|
18
|
+
# @return [Boolean] whether to verify the audience claim.
|
19
|
+
# @!attribute [rw] verify_sub
|
20
|
+
# @return [Boolean] whether to verify the subject claim.
|
21
|
+
# @!attribute [rw] leeway
|
22
|
+
# @return [Integer] the leeway in seconds for time-based claims.
|
23
|
+
# @!attribute [rw] algorithms
|
24
|
+
# @return [Array<String>] the list of acceptable algorithms.
|
25
|
+
# @!attribute [rw] required_claims
|
26
|
+
# @return [Array<String>] the list of required claims.
|
27
|
+
|
6
28
|
attr_accessor :verify_expiration,
|
7
29
|
:verify_not_before,
|
8
30
|
:verify_iss,
|
@@ -14,6 +36,7 @@ module JWT
|
|
14
36
|
:algorithms,
|
15
37
|
:required_claims
|
16
38
|
|
39
|
+
# Initializes a new DecodeConfiguration instance with default settings.
|
17
40
|
def initialize
|
18
41
|
@verify_expiration = true
|
19
42
|
@verify_not_before = true
|
@@ -27,6 +50,7 @@ module JWT
|
|
27
50
|
@required_claims = []
|
28
51
|
end
|
29
52
|
|
53
|
+
# @api private
|
30
54
|
def to_h
|
31
55
|
{
|
32
56
|
verify_expiration: verify_expiration,
|
data/lib/jwt/configuration.rb
CHANGED
@@ -3,11 +3,19 @@
|
|
3
3
|
require_relative 'configuration/container'
|
4
4
|
|
5
5
|
module JWT
|
6
|
+
# The Configuration module provides methods to configure JWT settings.
|
6
7
|
module Configuration
|
8
|
+
# Configures the JWT settings.
|
9
|
+
#
|
10
|
+
# @yield [config] Gives the current configuration to the block.
|
11
|
+
# @yieldparam config [JWT::Configuration::Container] the configuration container.
|
7
12
|
def configure
|
8
13
|
yield(configuration)
|
9
14
|
end
|
10
15
|
|
16
|
+
# Returns the JWT configuration container.
|
17
|
+
#
|
18
|
+
# @return [JWT::Configuration::Container] the configuration container.
|
11
19
|
def configuration
|
12
20
|
@configuration ||= ::JWT::Configuration::Container.new
|
13
21
|
end
|
data/lib/jwt/decode.rb
CHANGED
@@ -3,69 +3,67 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'jwt/x5c_key_finder'
|
5
5
|
|
6
|
-
# JWT::Decode module
|
7
6
|
module JWT
|
8
|
-
#
|
7
|
+
# The Decode class is responsible for decoding and verifying JWT tokens.
|
9
8
|
class Decode
|
9
|
+
# Initializes a new Decode instance.
|
10
|
+
#
|
11
|
+
# @param jwt [String] the JWT to decode.
|
12
|
+
# @param key [String, Array<String>] the key(s) to use for verification.
|
13
|
+
# @param verify [Boolean] whether to verify the token's signature.
|
14
|
+
# @param options [Hash] additional options for decoding and verification.
|
15
|
+
# @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification.
|
16
|
+
# @raise [JWT::DecodeError] if decoding or verification fails.
|
10
17
|
def initialize(jwt, key, verify, options, &keyfinder)
|
11
18
|
raise JWT::DecodeError, 'Nil JSON web token' unless jwt
|
12
19
|
|
13
|
-
@
|
20
|
+
@token = EncodedToken.new(jwt)
|
14
21
|
@key = key
|
15
22
|
@options = options
|
16
|
-
@segments = jwt.split('.')
|
17
23
|
@verify = verify
|
18
|
-
@signature = ''
|
19
24
|
@keyfinder = keyfinder
|
20
25
|
end
|
21
26
|
|
27
|
+
# Decodes the JWT token and verifies its segments if verification is enabled.
|
28
|
+
#
|
29
|
+
# @return [Array<Hash>] an array containing the decoded payload and header.
|
22
30
|
def decode_segments
|
23
31
|
validate_segment_count!
|
24
32
|
if @verify
|
25
|
-
decode_signature
|
26
33
|
verify_algo
|
27
34
|
set_key
|
28
35
|
verify_signature
|
29
|
-
|
36
|
+
Claims::DecodeVerifier.verify!(token.payload, @options)
|
30
37
|
end
|
31
|
-
raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
|
32
38
|
|
33
|
-
[payload, header]
|
39
|
+
[token.payload, token.header]
|
34
40
|
end
|
35
41
|
|
36
42
|
private
|
37
43
|
|
38
|
-
|
39
|
-
return unless @key || @verify
|
44
|
+
attr_reader :token
|
40
45
|
|
46
|
+
def verify_signature
|
41
47
|
return if none_algorithm?
|
42
48
|
|
43
49
|
raise JWT::DecodeError, 'No verification key available' unless @key
|
44
50
|
|
45
|
-
|
46
|
-
|
47
|
-
raise JWT::VerificationError, 'Signature verification failed'
|
51
|
+
token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key)
|
48
52
|
end
|
49
53
|
|
50
54
|
def verify_algo
|
51
55
|
raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
|
52
|
-
raise JWT::DecodeError, 'Token header not a JSON object' unless header.is_a?(Hash)
|
56
|
+
raise JWT::DecodeError, 'Token header not a JSON object' unless token.header.is_a?(Hash)
|
53
57
|
raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
|
54
58
|
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
|
55
59
|
end
|
56
60
|
|
57
61
|
def set_key
|
58
62
|
@key = find_key(&@keyfinder) if @keyfinder
|
59
|
-
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
|
63
|
+
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(token.header['kid']) if @options[:jwks]
|
60
64
|
return unless (x5c_options = @options[:x5c])
|
61
65
|
|
62
|
-
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
|
63
|
-
end
|
64
|
-
|
65
|
-
def verify_signature_for?(key)
|
66
|
-
allowed_and_valid_algorithms.any? do |alg|
|
67
|
-
alg.verify(data: signing_input, signature: @signature, verification_key: key)
|
68
|
-
end
|
66
|
+
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c'])
|
69
67
|
end
|
70
68
|
|
71
69
|
def allowed_and_valid_algorithms
|
@@ -91,70 +89,32 @@ module JWT
|
|
91
89
|
end
|
92
90
|
|
93
91
|
def resolve_allowed_algorithms
|
94
|
-
|
95
|
-
|
96
|
-
sort_by_alg_header(algs)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Move algorithms matching the JWT alg header to the beginning of the list
|
100
|
-
def sort_by_alg_header(algs)
|
101
|
-
return algs if algs.size <= 1
|
102
|
-
|
103
|
-
algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
|
92
|
+
given_algorithms.map { |alg| JWA.resolve(alg) }
|
104
93
|
end
|
105
94
|
|
106
95
|
def find_key(&keyfinder)
|
107
|
-
key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
|
96
|
+
key = (keyfinder.arity == 2 ? yield(token.header, token.payload) : yield(token.header))
|
108
97
|
# key can be of type [string, nil, OpenSSL::PKey, Array]
|
109
98
|
return key if key && !Array(key).empty?
|
110
99
|
|
111
100
|
raise JWT::DecodeError, 'No verification key available'
|
112
101
|
end
|
113
102
|
|
114
|
-
def verify_claims
|
115
|
-
Claims::DecodeVerifier.verify!(payload, @options)
|
116
|
-
end
|
117
|
-
|
118
103
|
def validate_segment_count!
|
119
|
-
|
120
|
-
return if
|
121
|
-
return if
|
104
|
+
segment_count = token.jwt.count('.') + 1
|
105
|
+
return if segment_count == 3
|
106
|
+
return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed
|
107
|
+
return if segment_count == 2 && none_algorithm?
|
122
108
|
|
123
109
|
raise JWT::DecodeError, 'Not enough or too many segments'
|
124
110
|
end
|
125
111
|
|
126
|
-
def segment_length
|
127
|
-
@segments.count
|
128
|
-
end
|
129
|
-
|
130
112
|
def none_algorithm?
|
131
113
|
alg_in_header == 'none'
|
132
114
|
end
|
133
115
|
|
134
|
-
def decode_signature
|
135
|
-
@signature = ::JWT::Base64.url_decode(@segments[2] || '')
|
136
|
-
end
|
137
|
-
|
138
116
|
def alg_in_header
|
139
|
-
header['alg']
|
140
|
-
end
|
141
|
-
|
142
|
-
def header
|
143
|
-
@header ||= parse_and_decode @segments[0]
|
144
|
-
end
|
145
|
-
|
146
|
-
def payload
|
147
|
-
@payload ||= parse_and_decode @segments[1]
|
148
|
-
end
|
149
|
-
|
150
|
-
def signing_input
|
151
|
-
@segments.first(2).join('.')
|
152
|
-
end
|
153
|
-
|
154
|
-
def parse_and_decode(segment)
|
155
|
-
JWT::JSON.parse(::JWT::Base64.url_decode(segment))
|
156
|
-
rescue ::JSON::ParserError
|
157
|
-
raise JWT::DecodeError, 'Invalid segment encoding'
|
117
|
+
token.header['alg']
|
158
118
|
end
|
159
119
|
end
|
160
120
|
end
|