jwt 2.9.2 → 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -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 +5 -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 +7 -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,15 +1,18 @@
|
|
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)
|
15
|
+
true
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
@@ -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
|