Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/jwt/claims.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class << self
# @param payload [Hash] the JWT payload.
# @param options [Array] the options for verifying the claims.
# @return [void]
# @raise [JWT::DecodeError] if any claim is invalid.
# @raise [JWT::ClaimValidationError] if any claim is invalid.
def verify_payload!(payload, *options)
Verifier.verify!(VerificationContext.new(payload: payload), *options)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/jwt/claims/verifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def errors(context, *options)
errors = []
iterate_verifiers(*options) do |verifier, verifier_options|
verify_one!(context, verifier, verifier_options)
rescue ::JWT::DecodeError => e
rescue ::JWT::ClaimValidationError => e
errors << Error.new(message: e.message)
end
errors
Expand Down
12 changes: 6 additions & 6 deletions lib/jwt/decode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class Decode
# @param verify [Boolean] whether to verify the token's signature.
# @param options [Hash] additional options for decoding and verification.
# @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification.
# @raise [JWT::DecodeError] if decoding or verification fails.
# @raise [JWT::Error] if decoding or verification fails.
def initialize(jwt, key, verify, options, &keyfinder)
raise JWT::DecodeError, 'Nil JSON web token' unless jwt
raise JWT::MalformedTokenError, 'Nil JSON web token' unless jwt

Comment thread
anakinj marked this conversation as resolved.
@token = EncodedToken.new(jwt)
@key = key
Expand Down Expand Up @@ -51,14 +51,14 @@ def decode_segments
def verify_signature
return if none_algorithm?

raise JWT::DecodeError, 'No verification key available' unless @key
raise JWT::SignatureError, 'No verification key available' unless @key

token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key)
end

def verify_algo
raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
raise JWT::DecodeError, 'Token header not a JSON object' unless valid_token_header?
raise JWT::MalformedTokenError, 'Token header not a JSON object' unless valid_token_header?
raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
end
Expand Down Expand Up @@ -100,7 +100,7 @@ def find_key(&keyfinder)
# key can be of type [string, nil, OpenSSL::PKey, Array]
return key if key && !Array(key).empty?

raise JWT::DecodeError, 'No verification key available'
raise JWT::SignatureError, 'No verification key available'
end

def validate_segment_count!
Expand All @@ -109,7 +109,7 @@ def validate_segment_count!
return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed
return if segment_count == 2 && none_algorithm?

raise JWT::DecodeError, 'Not enough or too many segments'
raise JWT::MalformedTokenError, 'Not enough or too many segments'
end

def none_algorithm?
Expand Down
14 changes: 7 additions & 7 deletions lib/jwt/encoded_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def header
# Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
#
# @return [Hash] the payload.
# @raise [JWT::DecodeError] if the signature has not been verified.
# @raise [JWT::Error] if the signature has not been verified.
def payload
raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified
raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified
raise JWT::Error, 'Verify the token signature before accessing the payload' unless @signature_verified
raise JWT::Error, 'Verify the token claims before accessing the payload' unless @claims_verified
Comment thread
anakinj marked this conversation as resolved.
Outdated

decoded_payload
end
Expand Down Expand Up @@ -98,7 +98,7 @@ def signing_input
# @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
# @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
# @return [nil]
# @raise [JWT::DecodeError] if the signature or claim verification fails.
# @raise [JWT::Error] if the signature or claim verification fails.
def verify!(signature:, claims: nil)
verify_signature!(**signature)
claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
Expand Down Expand Up @@ -152,7 +152,7 @@ def valid_signature?(algorithm: nil, key: nil, key_finder: nil)

# Verifies the claims of the token.
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
# @raise [JWT::DecodeError] if the claims are invalid.
# @raise [JWT::ClaimValidationError] if the claims are invalid.
def verify_claims!(*options)
Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do
@claims_verified = true
Expand Down Expand Up @@ -187,7 +187,7 @@ def claims_options(options)
end

def decode_payload
raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''
raise JWT::MalformedTokenError, 'Encoded payload is empty' if encoded_payload == ''

if unencoded_payload?
verify_claims!(crit: ['b64'])
Expand All @@ -212,7 +212,7 @@ def parse_unencoded(segment)
def parse(segment)
JWT::JSON.parse(segment)
rescue ::JSON::ParserError
raise JWT::DecodeError, 'Invalid segment encoding'
raise JWT::MalformedTokenError, 'Invalid segment encoding'
end

def decoded_payload
Expand Down
63 changes: 39 additions & 24 deletions lib/jwt/error.rb
Original file line number Diff line number Diff line change
@@ -1,54 +1,69 @@
# frozen_string_literal: true

module JWT
# The base error class for all JWT errors.
class Error < StandardError; end

# The EncodeError class is raised when there is an error encoding a JWT.
class EncodeError < StandardError; end
class EncodeError < Error; end

# The DecodeError class is raised when there is an error decoding a JWT.
class DecodeError < StandardError; end
# The TokenError class is the base class for all errors related to token processing.
class TokenError < Error; end

# The VerificationError class is raised when there is an error verifying a JWT.
class VerificationError < DecodeError; end
# The MalformedTokenError class is raised when the token is structurally invalid.
class MalformedTokenError < TokenError; end

# The ExpiredSignature class is raised when the JWT signature has expired.
class ExpiredSignature < DecodeError; end
# The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
class Base64DecodeError < MalformedTokenError; end

# The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
class IncorrectAlgorithm < DecodeError; end
# The SignatureError class is the base class for signature and algorithm related errors.
class SignatureError < TokenError; end

# The ImmatureSignature class is raised when the JWT signature is immature.
class ImmatureSignature < DecodeError; end
# The VerificationError class is raised when there is an error verifying a JWT signature.
class VerificationError < SignatureError; end

# The InvalidIssuerError class is raised when the JWT issuer is invalid.
class InvalidIssuerError < DecodeError; end
# The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
class IncorrectAlgorithm < SignatureError; end

# The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.
class UnsupportedEcdsaCurve < IncorrectAlgorithm; end

# The ClaimValidationError class is the base class for all claim validation errors.
class ClaimValidationError < TokenError; end

# The ExpiredSignature class is raised when the JWT token has expired.
class ExpiredSignature < ClaimValidationError; end

# The ImmatureSignature class is raised when the JWT token is not yet valid (nbf).
class ImmatureSignature < ClaimValidationError; end

# The InvalidIssuerError class is raised when the JWT issuer is invalid.
class InvalidIssuerError < ClaimValidationError; end

# The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.
class InvalidIatError < DecodeError; end
class InvalidIatError < ClaimValidationError; end

# The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.
class InvalidAudError < DecodeError; end
class InvalidAudError < ClaimValidationError; end

# The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.
class InvalidSubError < DecodeError; end
class InvalidSubError < ClaimValidationError; end

# The InvalidCritError class is raised when the JWT crit header is invalid.
class InvalidCritError < DecodeError; end
class InvalidCritError < ClaimValidationError; end

# The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.
class InvalidJtiError < DecodeError; end
class InvalidJtiError < ClaimValidationError; end

# The InvalidPayload class is raised when the JWT payload is invalid.
class InvalidPayload < DecodeError; end
class InvalidPayload < ClaimValidationError; end

# The MissingRequiredClaim class is raised when a required claim is missing from the JWT.
class MissingRequiredClaim < DecodeError; end

# The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
class Base64DecodeError < DecodeError; end
class MissingRequiredClaim < ClaimValidationError; end

# The JWKError class is raised when there is an error with the JSON Web Key (JWK).
class JWKError < DecodeError; end
class JWKError < Error; end

# Backwards compatibility alias
DecodeError = Error
Comment thread
anakinj marked this conversation as resolved.
Outdated
end
2 changes: 1 addition & 1 deletion lib/jwt/jwa/signing_algorithm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def verify(*)
end

def raise_verify_error!(message)
raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
raise(VerificationError.new(message).tap { |e| e.set_backtrace(caller(1)) })
end

def raise_sign_error!(message)
Expand Down
8 changes: 4 additions & 4 deletions lib/jwt/jwk/key_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def initialize(options)
# Returns the verification key for the given kid
# @param [String] kid the key id
def key_for(kid, key_field = :kid)
raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
raise ::JWT::SignatureError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
Comment thread
anakinj marked this conversation as resolved.
Outdated

jwk = resolve_key(kid, key_field)

raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
raise ::JWT::SignatureError, 'No keys found in jwks' unless @jwks.any?
raise ::JWT::SignatureError, "Could not find public key for kid #{kid}" unless jwk

jwk.verify_key
end
Expand All @@ -47,7 +47,7 @@ def call(token)
return key_for(field_value, key_field) if field_value
end

raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
raise ::JWT::SignatureError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
Comment thread
anakinj marked this conversation as resolved.

kid = token.header['kid']
key_for(kid)
Expand Down
2 changes: 1 addition & 1 deletion lib/jwt/token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def sign!(key:, algorithm:)

# Verifies the claims of the token.
# @param options [Array<Symbol>, Hash] the claims to verify.
# @raise [JWT::DecodeError] if the claims are invalid.
# @raise [JWT::ClaimValidationError] if the claims are invalid.
def verify_claims!(*options)
Claims::Verifier.verify!(self, *options)
end
Expand Down
97 changes: 97 additions & 0 deletions spec/jwt/error_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

RSpec.describe 'JWT error hierarchy' do
context 'base classes' do
it 'JWT::Error inherits from StandardError' do
expect(JWT::Error).to be < StandardError
end

it 'JWT::EncodeError inherits from JWT::Error' do
expect(JWT::EncodeError).to be < JWT::Error
end

it 'JWT::TokenError inherits from JWT::Error' do
expect(JWT::TokenError).to be < JWT::Error
end
end

context 'backwards compatibility' do
it 'JWT::DecodeError is an alias for JWT::Error' do
expect(JWT::DecodeError).to eq(JWT::Error)
Comment thread
anakinj marked this conversation as resolved.
end
end

context 'malformed token errors' do
it 'JWT::MalformedTokenError inherits from JWT::TokenError' do
expect(JWT::MalformedTokenError).to be < JWT::TokenError
end

it 'JWT::Base64DecodeError inherits from JWT::MalformedTokenError' do
expect(JWT::Base64DecodeError).to be < JWT::MalformedTokenError
end
end

context 'signature errors' do
it 'JWT::SignatureError inherits from JWT::TokenError' do
expect(JWT::SignatureError).to be < JWT::TokenError
end

it 'JWT::VerificationError inherits from JWT::SignatureError' do
expect(JWT::VerificationError).to be < JWT::SignatureError
end

it 'JWT::IncorrectAlgorithm inherits from JWT::SignatureError' do
expect(JWT::IncorrectAlgorithm).to be < JWT::SignatureError
end

it 'JWT::UnsupportedEcdsaCurve inherits from JWT::IncorrectAlgorithm' do
expect(JWT::UnsupportedEcdsaCurve).to be < JWT::IncorrectAlgorithm
end
end

context 'claim validation errors' do
it 'JWT::ClaimValidationError inherits from JWT::TokenError' do
expect(JWT::ClaimValidationError).to be < JWT::TokenError
end

%i[
ExpiredSignature
ImmatureSignature
InvalidIssuerError
InvalidIatError
InvalidAudError
InvalidSubError
InvalidCritError
InvalidJtiError
InvalidPayload
MissingRequiredClaim
].each do |error_class|
it "JWT::#{error_class} inherits from JWT::ClaimValidationError" do
expect(JWT.const_get(error_class)).to be < JWT::ClaimValidationError
end
end
end

context 'JWK errors' do
it 'JWT::JWKError inherits from JWT::Error' do
expect(JWT::JWKError).to be < JWT::Error
end
end

context 'error groups do not overlap' do
it 'claim validation errors are not signature errors' do
expect(JWT::ClaimValidationError).not_to be <= JWT::SignatureError
expect(JWT::SignatureError).not_to be <= JWT::ClaimValidationError
end

it 'claim validation errors are not malformed token errors' do
expect(JWT::ClaimValidationError).not_to be <= JWT::MalformedTokenError
expect(JWT::MalformedTokenError).not_to be <= JWT::ClaimValidationError
end

it 'signature errors are not malformed token errors' do
expect(JWT::SignatureError).not_to be <= JWT::MalformedTokenError
expect(JWT::MalformedTokenError).not_to be <= JWT::SignatureError
end
end
end
Loading