note description: "Loader and verifier to JWT token." date: "$Date$" revision: "$Revision$" EIS: "name=Known Critical vulnerabilities in JWT libs", "protocol=URI", "src=https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/" class JWT_LOADER inherit JWT_UTILITIES redefine default_create end feature {NONE} -- Initialization default_create do create algorithms end feature -- Settings algorithms: JWT_ALGORITHMS feature -- Access token (a_token_input: READABLE_STRING_8; a_alg: detachable READABLE_STRING_8; a_verification_key: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT -- Decoded token from `a_token_input` given the verification key `a_verification_key` and optional (but recommended) signature algorithm `a_alg`, and optional context `ctx` -- used to specify eventual issuer and various parameters. -- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens, -- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification! require a_valid_alg: a_alg /= Void implies algorithms.is_supported_algorithm (a_alg) local jws: JWS i,j,n: INTEGER alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8 alg_encoder: JWT_ALG do n := a_token_input.count i := a_token_input.index_of ('.', 1) if i > 0 then j := a_token_input.index_of ('.', i + 1) if j > 0 then l_enc_header := adjusted_string (a_token_input.substring (1, i - 1)) l_enc_payload := adjusted_string (a_token_input.substring (i + 1, j - 1)) l_signature := adjusted_string (a_token_input.substring (j + 1, n)) create jws.make_with_json_payload (base64url_decode (l_enc_payload)) alg := signature_algorithm_from_encoded_header (l_enc_header) if a_alg /= Void then if alg /= Void and then not alg.is_case_insensitive_equal_general (a_alg) then jws.report_mismatched_alg_error (a_alg, alg) else alg := a_alg end else if alg = Void then -- Use default alg := algorithms.default_algorithm.name end end jws.set_algorithm (alg) check alg_set: alg /= Void end if ctx = Void or else not ctx.validation_ignored then alg_encoder := algorithms [alg] if alg_encoder = Void then jws.report_unsupported_alg_error (alg) alg_encoder := algorithms.default_algorithm end if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg_encoder)) then jws.report_unverified_token_error end if not jws.has_error and then ctx /= Void then check not ctx.validation_ignored end if jws.is_expired (ctx.time) then jws.report_claim_validation_error ("exp") end if not jws.is_nbf_validated (ctx.time) then jws.report_claim_validation_error ("nbf") end if not jws.is_iss_validated (ctx.issuer) then jws.report_claim_validation_error ("iss") end if not jws.is_aud_validated (ctx.audience) then jws.report_claim_validation_error ("aud") end end end else -- jws.report_invalid_token end else -- jws.report_invalid_token end Result := jws end feature {NONE} -- Implementation adjusted_string (a_string: READABLE_STRING_8): READABLE_STRING_8 -- `a_string` without any space. local s: STRING_8 do if not a_string.is_empty and then (a_string [1].is_space or a_string [a_string.count].is_space) then s := a_string.to_string_8 s.adjust Result := s else Result := a_string end end signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8 local jp: JSON_PARSER do create jp.make_with_string (base64url_decode (a_enc_header)) jp.parse_content if attached jp.parsed_json_object as jo and then attached {JSON_STRING} jo.item ("alg") as j_alg then Result := j_alg.unescaped_string_8 end end end