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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -16
  3. data/README.md +153 -83
  4. data/lib/jwt/base64.rb +3 -0
  5. data/lib/jwt/claims/audience.rb +10 -0
  6. data/lib/jwt/claims/crit.rb +35 -0
  7. data/lib/jwt/claims/decode_verifier.rb +3 -3
  8. data/lib/jwt/claims/expiration.rb +10 -0
  9. data/lib/jwt/claims/issued_at.rb +7 -0
  10. data/lib/jwt/claims/issuer.rb +10 -0
  11. data/lib/jwt/claims/jwt_id.rb +10 -0
  12. data/lib/jwt/claims/not_before.rb +10 -0
  13. data/lib/jwt/claims/numeric.rb +22 -0
  14. data/lib/jwt/claims/required.rb +10 -0
  15. data/lib/jwt/claims/subject.rb +10 -0
  16. data/lib/jwt/claims/verification_methods.rb +20 -0
  17. data/lib/jwt/claims/verifier.rb +6 -7
  18. data/lib/jwt/claims.rb +6 -14
  19. data/lib/jwt/claims_validator.rb +4 -2
  20. data/lib/jwt/configuration/container.rb +20 -0
  21. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  23. data/lib/jwt/configuration.rb +8 -0
  24. data/lib/jwt/decode.rb +28 -68
  25. data/lib/jwt/deprecations.rb +1 -0
  26. data/lib/jwt/encode.rb +17 -56
  27. data/lib/jwt/encoded_token.rb +139 -0
  28. data/lib/jwt/error.rb +34 -0
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/compat.rb +3 -0
  31. data/lib/jwt/jwa/ecdsa.rb +3 -6
  32. data/lib/jwt/jwa/eddsa.rb +7 -6
  33. data/lib/jwt/jwa/hmac.rb +2 -3
  34. data/lib/jwt/jwa/hmac_rbnacl.rb +1 -0
  35. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +1 -0
  36. data/lib/jwt/jwa/none.rb +1 -0
  37. data/lib/jwt/jwa/ps.rb +2 -3
  38. data/lib/jwt/jwa/rsa.rb +2 -3
  39. data/lib/jwt/jwa/signing_algorithm.rb +3 -0
  40. data/lib/jwt/jwa/unsupported.rb +1 -0
  41. data/lib/jwt/jwa/wrapper.rb +1 -0
  42. data/lib/jwt/jwa.rb +11 -3
  43. data/lib/jwt/jwk/ec.rb +2 -3
  44. data/lib/jwt/jwk/hmac.rb +2 -3
  45. data/lib/jwt/jwk/key_base.rb +1 -0
  46. data/lib/jwt/jwk/key_finder.rb +1 -0
  47. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  48. data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
  49. data/lib/jwt/jwk/rsa.rb +2 -3
  50. data/lib/jwt/jwk/set.rb +2 -0
  51. data/lib/jwt/jwk.rb +1 -0
  52. data/lib/jwt/token.rb +112 -0
  53. data/lib/jwt/verify.rb +6 -0
  54. data/lib/jwt/version.rb +33 -10
  55. data/lib/jwt.rb +16 -0
  56. 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')
@@ -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')
@@ -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
@@ -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')
@@ -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
 
@@ -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)
@@ -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
@@ -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
- # @private
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/decode_verifier'
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
- verify_token!(VerificationContext.new(payload: payload), *options)
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
- token_errors(VerificationContext.new(payload: payload), *options)
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
@@ -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,
@@ -5,6 +5,7 @@ require_relative '../jwk/thumbprint'
5
5
 
6
6
  module JWT
7
7
  module Configuration
8
+ # @api private
8
9
  class JwkConfiguration
9
10
  def initialize
10
11
  self.kid_generator_type = :key_digest
@@ -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
- # Decoding logic for JWT
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
- @jwt = jwt
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
- verify_claims
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
- def verify_signature
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
- return if Array(@key).any? { |key| verify_signature_for?(key) }
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
- algs = given_algorithms.map { |alg| JWA.resolve(alg) }
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
- return if segment_length == 3
120
- return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
121
- return if segment_length == 2 && none_algorithm?
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  # Deprecations module to handle deprecation warnings in the gem
5
+ # @api private
5
6
  module Deprecations
6
7
  class << self
7
8
  def context