11# frozen_string_literal: true
22
3+ require_relative 'encoded_token/claims_context'
4+ require_relative 'encoded_token/segment_parser'
5+ require_relative 'encoded_token/signature_verifier'
6+
37module JWT
48 # Represents an encoded JWT token
59 #
@@ -11,31 +15,26 @@ module JWT
1115 # encoded_token = JWT::EncodedToken.new(token.jwt)
1216 # encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
1317 # encoded_token.payload # => {'pay' => 'load'}
14- class EncodedToken # rubocop:disable Metrics/ClassLength
15- # @private
16- # Allow access to the unverified payload for claim verification.
17- class ClaimsContext
18- extend Forwardable
19-
20- def_delegators :@token , :header , :unverified_payload
21-
22- def initialize ( token )
23- @token = token
24- end
25-
26- def payload
27- unverified_payload
28- end
29- end
30-
18+ class EncodedToken
3119 DEFAULT_CLAIMS = [ :exp ] . freeze
32-
3320 private_constant ( :DEFAULT_CLAIMS )
3421
3522 # Returns the original token provided to the class.
3623 # @return [String] The JWT token.
3724 attr_reader :jwt
3825
26+ # Returns the encoded signature of the JWT token.
27+ # @return [String] the encoded signature.
28+ attr_reader :encoded_signature
29+
30+ # Returns the encoded header of the JWT token.
31+ # @return [String] the encoded header.
32+ attr_reader :encoded_header
33+
34+ # Sets or returns the encoded payload of the JWT token.
35+ # @return [String] the encoded payload.
36+ attr_accessor :encoded_payload
37+
3938 # Initializes a new EncodedToken instance.
4039 #
4140 # @param jwt [String] the encoded JWT token.
@@ -46,165 +45,109 @@ def initialize(jwt)
4645 @jwt = jwt
4746 @allow_duplicate_keys = true
4847 @signature_verified = false
49- @claims_verified = false
50-
48+ @claims_verified = false
5149 @encoded_header , @encoded_payload , @encoded_signature = jwt . split ( '.' )
5250 end
5351
5452 # Enables strict duplicate key detection for this token.
55- # When called, the token will raise JWT::DuplicateKeyError if duplicate keys
56- # are found in the header or payload during parsing.
57- #
58- # @example
59- # token = JWT::EncodedToken.new(jwt_string)
60- # token.raise_on_duplicate_keys!
61- # token.header # May raise JWT::DuplicateKeyError
62- #
6353 # @return [self]
64- # @raise [JWT::DuplicateKeyError] if duplicate keys are found during subsequent parsing.
6554 # @raise [JWT::UnsupportedError] if the JSON gem version does not support duplicate key detection.
6655 def raise_on_duplicate_keys!
6756 raise JWT ::UnsupportedError , 'Duplicate key detection requires JSON gem >= 2.13.0' unless JSON . supports_duplicate_key_detection?
6857
6958 @allow_duplicate_keys = false
59+ @parser = nil
7060 self
7161 end
7262
7363 # Returns the decoded signature of the JWT token.
74- #
7564 # @return [String] the decoded signature.
7665 def signature
7766 @signature ||= ::JWT ::Base64 . url_decode ( encoded_signature || '' )
7867 end
7968
80- # Returns the encoded signature of the JWT token.
81- #
82- # @return [String] the encoded signature.
83- attr_reader :encoded_signature
84-
8569 # Returns the decoded header of the JWT token.
86- #
8770 # @return [Hash] the header.
8871 def header
89- @header ||= parse_and_decode ( @encoded_header )
72+ @header ||= parser . parse_and_decode ( @encoded_header )
9073 end
9174
92- # Returns the encoded header of the JWT token.
93- #
94- # @return [String] the encoded header.
95- attr_reader :encoded_header
96-
9775 # Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
98- #
9976 # @return [Hash] the payload.
100- # @raise [JWT::DecodeError] if the signature has not been verified.
77+ # @raise [JWT::DecodeError] if the signature or claims have not been verified.
10178 def payload
10279 raise JWT ::DecodeError , 'Verify the token signature before accessing the payload' unless @signature_verified
10380 raise JWT ::DecodeError , 'Verify the token claims before accessing the payload' unless @claims_verified
10481
105- decoded_payload
82+ unverified_payload
10683 end
10784
10885 # Returns the payload of the JWT token without requiring the signature to have been verified.
10986 # @return [Hash] the payload.
11087 def unverified_payload
111- decoded_payload
88+ @unverified_payload ||= decode_payload
11289 end
11390
114- # Sets or returns the encoded payload of the JWT token.
115- #
116- # @return [String] the encoded payload.
117- attr_accessor :encoded_payload
118-
11991 # Returns the signing input of the JWT token.
120- #
12192 # @return [String] the signing input.
12293 def signing_input
12394 [ encoded_header , encoded_payload ] . join ( '.' )
12495 end
12596
12697 # Verifies the token signature and claims.
127- # By default it verifies the 'exp' claim.
128- #
129- # @example
130- # encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp])
131- #
132- # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
133- # @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
98+ # @param signature [Hash] the parameters for signature verification.
99+ # @param claims [Array<Symbol>, Hash] the claims to verify.
134100 # @return [nil]
135- # @raise [JWT::DecodeError] if the signature or claim verification fails.
136101 def verify! ( signature :, claims : nil )
137102 verify_signature! ( **signature )
138103 claims . is_a? ( Array ) ? verify_claims! ( *claims ) : verify_claims! ( claims )
139104 nil
140105 end
141106
142107 # Verifies the token signature and claims.
143- # By default it verifies the 'exp' claim.
144-
145- # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
146- # @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
147- # @return [Boolean] true if the signature and claims are valid, false otherwise.
108+ # @param signature [Hash] the parameters for signature verification.
109+ # @param claims [Array<Symbol>, Hash] the claims to verify.
110+ # @return [Boolean] true if the signature and claims are valid.
148111 def valid? ( signature :, claims : nil )
149- valid_signature? ( **signature ) &&
150- ( claims . is_a? ( Array ) ? valid_claims? ( *claims ) : valid_claims? ( claims ) )
112+ valid_signature? ( **signature ) && ( claims . is_a? ( Array ) ? valid_claims? ( *claims ) : valid_claims? ( claims ) )
151113 end
152114
153115 # Verifies the signature of the JWT token.
154- #
155- # @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
156- # @param key [String, Array<String>] the key(s) to use for verification.
157- # @param key_finder [#call] an object responding to `call` to find the key for verification.
158116 # @return [nil]
159117 # @raise [JWT::VerificationError] if the signature verification fails.
160- # @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided.
161118 def verify_signature! ( algorithm :, key : nil , key_finder : nil )
162119 return if valid_signature? ( algorithm : algorithm , key : key , key_finder : key_finder )
163120
164121 raise JWT ::VerificationError , 'Signature verification failed'
165122 end
166123
167124 # Checks if the signature of the JWT token is valid.
168- #
169- # @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
170- # @param key [String, Array<String>, JWT::JWK::KeyBase, Array<JWT::JWK::KeyBase>] the key(s) to use for verification.
171- # @param key_finder [#call] an object responding to `call` to find the key for verification.
172- # @return [Boolean] true if the signature is valid, false otherwise.
125+ # @return [Boolean] true if the signature is valid.
173126 def valid_signature? ( algorithm : nil , key : nil , key_finder : nil )
174- raise ArgumentError , 'Provide either key or key_finder, not both or neither' if key . nil? == key_finder . nil?
175-
176- keys = Array ( key || key_finder . call ( self ) )
177- verifiers = JWA . create_verifiers ( algorithms : algorithm , keys : keys , preferred_algorithm : header [ 'alg' ] )
178-
179- raise JWT ::VerificationError , 'No algorithm provided' if verifiers . empty?
180-
181- valid = verifiers . any? do |jwa |
182- jwa . verify ( data : signing_input , signature : signature )
127+ SignatureVerifier . new ( self ) . verify ( algorithm : algorithm , key : key , key_finder : key_finder ) . tap do |valid |
128+ @signature_verified = valid
183129 end
184- valid . tap { |verified | @signature_verified = verified }
185130 end
186131
187132 # Verifies the claims of the token.
188- # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
133+ # @param options [Array<Symbol>, Hash] the claims to verify.
189134 # @raise [JWT::DecodeError] if the claims are invalid.
190135 def verify_claims! ( *options )
191- Claims ::Verifier . verify! ( ClaimsContext . new ( self ) , *claims_options ( options ) ) . tap do
192- @claims_verified = true
193- end
136+ Claims ::Verifier . verify! ( ClaimsContext . new ( self ) , *claims_options ( options ) ) . tap { @claims_verified = true }
194137 rescue StandardError
195138 @claims_verified = false
196139 raise
197140 end
198141
199142 # Returns the errors of the claims of the token.
200- # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
143+ # @param options [Array<Symbol>, Hash] the claims to verify.
201144 # @return [Array<Symbol>] the errors of the claims.
202145 def claim_errors ( *options )
203146 Claims ::Verifier . errors ( ClaimsContext . new ( self ) , *claims_options ( options ) )
204147 end
205148
206149 # Returns whether the claims of the token are valid.
207- # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
150+ # @param options [Array<Symbol>, Hash] the claims to verify.
208151 # @return [Boolean] whether the claims are valid.
209152 def valid_claims? ( *options )
210153 claim_errors ( *claims_options ( options ) ) . empty? . tap { |verified | @claims_verified = verified }
@@ -215,42 +158,19 @@ def valid_claims?(*options)
215158 private
216159
217160 def claims_options ( options )
218- return DEFAULT_CLAIMS if options . first . nil?
161+ options . first . nil? ? DEFAULT_CLAIMS : options
162+ end
219163
220- options
164+ def parser
165+ @parser ||= SegmentParser . new ( allow_duplicate_keys : @allow_duplicate_keys )
221166 end
222167
223168 def decode_payload
224169 raise JWT ::DecodeError , 'Encoded payload is empty' if encoded_payload == ''
225170
226- if unencoded_payload?
227- verify_claims! ( crit : [ 'b64' ] )
228- return parse_unencoded ( encoded_payload )
229- end
230-
231- parse_and_decode ( encoded_payload )
232- end
233-
234- def unencoded_payload?
235- header [ 'b64' ] == false
236- end
237-
238- def parse_and_decode ( segment )
239- parse ( ::JWT ::Base64 . url_decode ( segment || '' ) )
240- end
241-
242- def parse_unencoded ( segment )
243- parse ( segment )
244- end
245-
246- def parse ( segment )
247- JWT ::JSON . parse ( segment , allow_duplicate_keys : @allow_duplicate_keys )
248- rescue ::JSON ::ParserError
249- raise JWT ::DecodeError , 'Invalid segment encoding'
250- end
171+ return parser . parse_unencoded ( encoded_payload ) . tap { verify_claims! ( crit : [ 'b64' ] ) } if header [ 'b64' ] == false
251172
252- def decoded_payload
253- @decoded_payload ||= decode_payload
173+ parser . parse_and_decode ( encoded_payload )
254174 end
255175 end
256176end
0 commit comments