diff --git a/library/security/jwt/README.md b/library/security/jwt/README.md
new file mode 100644
index 00000000..c5bfd597
--- /dev/null
+++ b/library/security/jwt/README.md
@@ -0,0 +1,26 @@
+JSON Web Token (JWT)
+
+http://jwt.io/
+
+Note: supporting only HS256 and none algorithm for signature.
+
+# How to use
+```eiffel
+ local
+ jwt: JWT
+ do
+ create jwt
+ tok := jwt.encoded_string ("[
+ {"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
+ ]", "secret", "HS256")
+ if
+ attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload and
+ not jwt.has_error
+ then
+ check verified: not jwt.has_unverified_token_error end
+ check no_error: not jwt.has_error end
+ print (l_tok_payload)
+ end
+ end
+```
+
diff --git a/library/security/jwt/jwt.ecf b/library/security/jwt/jwt.ecf
new file mode 100644
index 00000000..66c4fe63
--- /dev/null
+++ b/library/security/jwt/jwt.ecf
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/security/jwt/package.iron b/library/security/jwt/package.iron
new file mode 100644
index 00000000..ef42b57a
--- /dev/null
+++ b/library/security/jwt/package.iron
@@ -0,0 +1,16 @@
+package jwt
+
+project
+ jwt = "jwt.ecf"
+
+note
+ title: JSON Web Token
+ description: JSON Web Token
+ tags:jwt,web,jws,jwe,token
+ copyright: 2011-2016, Jocelyn Fiat, Eiffel Software and others
+ license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
+ link[license]: http://www.eiffel.com/licensing/forum.txt
+ link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/security/jwt
+ link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/tree/master/library/security/jwt/README.md
+
+end
diff --git a/library/security/jwt/src/jwt.e b/library/security/jwt/src/jwt.e
new file mode 100644
index 00000000..147160c8
--- /dev/null
+++ b/library/security/jwt/src/jwt.e
@@ -0,0 +1,265 @@
+note
+ description: "JSON Web Token"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ JWT
+
+feature -- Initialization
+
+ encoded_string (a_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
+ local
+ alg, sign: STRING_8
+ l_enc_payload, l_enc_header: READABLE_STRING_8
+ do
+ reset_error
+ if a_algo.is_case_insensitive_equal_general (alg_hs256) then
+ alg := alg_hs256
+ elseif a_algo.is_case_insensitive_equal_general (alg_none) then
+ alg := alg_none
+ else
+ report_unsupported_alg_error (a_algo)
+ alg := alg_hs256 -- Default ...
+ end
+ l_enc_header := base64url_encode (header ("JWT", alg))
+ l_enc_payload := base64url_encode (a_payload)
+ sign := signature (l_enc_header, l_enc_payload, a_secret, alg)
+ create Result.make (l_enc_header.count + 1 + l_enc_payload.count + 1 + sign.count)
+ Result.append (l_enc_header)
+ Result.append_character ('.')
+ Result.append (l_enc_payload)
+ Result.append_character ('.')
+ Result.append (sign)
+ end
+
+ decoded_string (a_token: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: detachable READABLE_STRING_8): detachable STRING
+ local
+ i,j,n: INTEGER
+ alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
+ do
+ reset_error
+ n := a_token.count
+ i := a_token.index_of ('.', 1)
+ if i > 0 then
+ j := a_token.index_of ('.', i + 1)
+ if j > 0 then
+ l_enc_header := a_token.substring (1, i - 1)
+ l_enc_payload := a_token.substring (i + 1, j - 1)
+ l_signature := a_token.substring (j + 1, n)
+ Result := base64url_decode (l_enc_payload)
+ alg := a_algo
+ if alg = Void then
+ alg := signature_algorithm_from_encoded_header (l_enc_header)
+ if alg = Void then
+ -- Use default
+ alg := alg_hs256
+ end
+ end
+ check alg_set: alg /= Void end
+ if alg.is_case_insensitive_equal (alg_hs256) then
+ alg := alg_hs256
+ elseif alg.is_case_insensitive_equal (alg_none) then
+ alg := alg_none
+ else
+ alg := alg_hs256
+ report_unsupported_alg_error (alg)
+ end
+
+ if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
+ report_unverified_token_error
+ end
+ else
+ report_invalid_token
+ end
+ else
+ report_invalid_token
+ end
+ end
+
+feature -- Status report
+
+ supported_signature_algorithms: LIST [READABLE_STRING_8]
+ -- Supported signature algorithm `alg`?
+ do
+ create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (2)
+ Result.extend (alg_hs256)
+ Result.extend (alg_none)
+ end
+
+ is_supporting_signature_algorithm (alg: READABLE_STRING_8): BOOLEAN
+ -- Is supporting signature algorithm `alg`?
+ do
+ Result := alg.is_case_insensitive_equal (alg_hs256) or
+ alg.is_case_insensitive_equal (alg_none)
+ end
+
+ error_code: INTEGER
+ -- Last error, if any.
+
+ has_error: BOOLEAN
+ -- Last `encoded_string` reported an error?
+ do
+ Result := error_code /= 0
+ end
+
+ has_unsupported_alg_error: BOOLEAN
+ do
+ Result := error_code = unsupported_alg_error
+ end
+
+ has_unverified_token_error: BOOLEAN
+ do
+ Result := error_code = unverified_token_error
+ end
+
+ has_invalid_token_error: BOOLEAN
+ do
+ Result := error_code = invalid_token_error
+ end
+
+feature -- Error reporting
+
+ reset_error
+ do
+ error_code := 0
+ end
+
+ report_unsupported_alg_error (alg: READABLE_STRING_8)
+ do
+ error_code := unsupported_alg_error
+ end
+
+ report_unverified_token_error
+ do
+ error_code := unverified_token_error
+ end
+
+ report_invalid_token
+ do
+ error_code := invalid_token_error
+ end
+
+feature {NONE} -- Constants
+
+ unsupported_alg_error: INTEGER = -2
+
+ unverified_token_error: INTEGER = -4
+
+ invalid_token_error: INTEGER = -8
+
+ alg_hs256: STRING = "HS256"
+ -- HMAC SHA256.
+
+ alg_none: STRING = "none"
+ -- for unsecured token.
+
+feature -- Conversion
+
+ header (a_type: detachable READABLE_STRING_8; alg: READABLE_STRING_8): STRING
+ do
+ create Result.make_empty
+ Result.append ("{%"typ%":%"")
+ if a_type /= Void then
+ Result.append (a_type)
+ else
+ Result.append ("JWT")
+ end
+ Result.append ("%",%"alg%":%"")
+ Result.append (alg)
+ Result.append ("%"}")
+ 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
+
+feature -- Implementation
+
+ base64url_encode (s: READABLE_STRING_8): STRING_8
+ local
+ urlencoder: URL_ENCODER
+ base64: BASE64
+ do
+ create urlencoder
+ create base64
+ Result := urlsafe_encode (base64.encoded_string (s))
+ end
+
+feature {NONE} -- Implementation
+
+ signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
+ local
+ s: STRING
+ do
+ if alg = alg_none then
+ create Result.make_empty
+ else
+ create s.make (a_enc_header.count + 1 + a_enc_payload.count)
+ s.append (a_enc_header)
+ s.append_character ('.')
+ s.append (a_enc_payload)
+ if alg = alg_hs256 then
+ Result := base64_hmacsha256 (s, a_secret)
+ else
+ Result := base64_hmacsha256 (s, a_secret)
+ end
+ Result := urlsafe_encode (Result)
+ end
+ end
+
+ base64url_decode (s: READABLE_STRING_8): STRING_8
+ local
+ urlencoder: URL_ENCODER
+ base64: BASE64
+ do
+ create urlencoder
+ create base64
+ Result := base64.decoded_string (urlsafe_decode (s))
+ end
+
+ urlsafe_encode (s: READABLE_STRING_8): STRING_8
+ do
+ create Result.make_from_string (s)
+ Result.replace_substring_all ("=", "")
+ Result.replace_substring_all ("+", "-")
+ Result.replace_substring_all ("/", "_")
+ end
+
+ urlsafe_decode (s: READABLE_STRING_8): STRING_8
+ local
+ i: INTEGER
+ do
+ create Result.make_from_string (s)
+ Result.replace_substring_all ("-", "+")
+ Result.replace_substring_all ("_", "/")
+ from
+ i := Result.count \\ 4
+ until
+ i = 0
+ loop
+ i := i - 1
+ Result.extend ('=')
+ end
+ end
+
+ base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
+ local
+ hs256: HMAC_SHA256
+ do
+ create hs256.make_ascii_key (a_secret)
+ hs256.update_from_string (s)
+ Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
+ end
+
+end
diff --git a/library/security/jwt/testing/test_jwt.e b/library/security/jwt/testing/test_jwt.e
new file mode 100644
index 00000000..2b269762
--- /dev/null
+++ b/library/security/jwt/testing/test_jwt.e
@@ -0,0 +1,79 @@
+note
+ description: "Summary description for {TEST_JWT}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ TEST_JWT
+
+inherit
+ EQA_TEST_SET
+
+feature -- Test
+
+ test_jwt_io
+ local
+ jwt: JWT
+ header: STRING
+ payload: STRING
+ do
+ payload := "[
+ {"sub":"1234567890","name":"John Doe","admin":true}
+ ]"
+ payload.adjust
+ payload.replace_substring_all ("%N", "%R%N")
+
+ create jwt
+
+ assert ("header", jwt.base64url_encode (jwt.header (Void, "HS256")).same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"))
+ assert ("payload", jwt.base64url_encode (payload).same_string ("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"))
+ assert ("signature", jwt.encoded_string (payload, "secret", "HS256").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
+ end
+
+ test_jwt
+ local
+ jwt: JWT
+ payload: STRING
+ tok: STRING
+ do
+ payload := "[
+ {"iss":"joe",
+ "exp":1300819380,
+ "http://example.com/is_root":true}
+ ]"
+
+-- payload := "[
+-- {"sub":"1234567890","name":"John Doe","admin":true}
+-- ]"
+
+ create jwt
+ tok := jwt.encoded_string (payload, "secret", "HS256")
+
+ if attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload then
+ assert ("no error", not jwt.has_error)
+ assert ("same payload", l_tok_payload.same_string (payload))
+ end
+ end
+
+ test_unsecured_jwt
+ local
+ jwt: JWT
+ payload: STRING
+ tok: STRING
+ do
+ payload := "[
+ {"iss":"joe",
+ "exp":1300819380,
+ "http://example.com/is_root":true}
+ ]"
+
+ create jwt
+ tok := jwt.encoded_string (payload, "secret", "none")
+
+ if attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload then
+ assert ("no error", not jwt.has_error)
+ assert ("same payload", l_tok_payload.same_string (payload))
+ end
+ end
+
+end
diff --git a/library/security/jwt/testing/testing.ecf b/library/security/jwt/testing/testing.ecf
new file mode 100644
index 00000000..3358cbe3
--- /dev/null
+++ b/library/security/jwt/testing/testing.ecf
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+