diff --git a/hiboo/captcha/fields.py b/hiboo/captcha/fields.py
index 35ee8d1f6b025a426cb44cdafbdbfa534be55285..efa0de02c33188e7ff1cbd9cefc1cd6116ce700a 100644
--- a/hiboo/captcha/fields.py
+++ b/hiboo/captcha/fields.py
@@ -1,36 +1,49 @@
 from wtforms import validators, fields, widgets, utils
-from jwcrypto import jwe, common
+from joserfc import jwk, jwt
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
 
 import flask
 import random
+import datetime
 
 
 class ContextTokenField(fields.Field):
     """ Field that has a token hidden field that holds a stateless context.
     """
 
-    # TODO: implement context expiration
-    # TODO: implement context validity based on request attributes
-
     @classmethod
     def encode(cls, context):
-        """ JOSE authenticating encryption
-        """
-        jose = jwe.JWE(
-            common.json_encode(context),
-            common.json_encode({"alg": "PBES2-HS512+A256KW", "enc": "A256GCM"})
-        )
-        jose.add_recipient(flask.current_app.config["SECRET_KEY"])
-        return jose.serialize(True)
+        """JOSE authenticating encryption"""
+        kdf = HKDF(hashes.SHA512(), length=32, salt=None, info=None)
+        dkey = kdf.derive(flask.current_app.config["SECRET_KEY"].encode())
+        key = jwk.OctKey.import_key(dkey)
+        header = {"alg": "A256KW", "enc": "A256GCM"}
+        expired = datetime.datetime.now() + datetime.timedelta(minutes=3)
+        claims = {
+            "exp": int(expired.timestamp()),
+            "aud": flask.url_for('account.signup'),
+            "context": context
+        }
+
+        return jwt.encode(header, claims, key)
 
     @classmethod
-    def decode(cls, serialized):
-        """ JOSE decryption
-        """
-        jose = jwe.JWE()
-        jose.deserialize(serialized)
-        jose.decrypt(flask.current_app.config["SECRET_KEY"])
-        return common.json_decode(jose.payload)
+    def decode(cls, token):
+        """JOSE decryption"""
+        kdf = HKDF(hashes.SHA512(), length=32, salt=None, info=None)
+        dkey = kdf.derive(flask.current_app.config["SECRET_KEY"].encode())
+        key = jwk.OctKey.import_key(dkey)
+        decoded_token = jwt.decode(token, key)
+        claims_requests = jwt.JWTClaimsRegistry(
+            now=int(datetime.datetime.now().timestamp()),
+            aud={'essential': True, 'value': flask.url_for('account.signup')},
+            exp={'essential': True},
+            context={'essential': True}
+        )
+        claims_requests.validate(decoded_token.claims)
+
+        return decoded_token.claims["context"]
 
     def __init__(self, *args, **kwargs):
         self.received_context = {}
diff --git a/poetry.lock b/poetry.lock
index 380120ffe417821fb777f1c280dc4596fd911d9b..3bd72dce5cd21297645a6889db07f0e4c5908fb5 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -843,18 +843,21 @@ MarkupSafe = ">=2.0"
 i18n = ["Babel (>=2.7)"]
 
 [[package]]
-name = "jwcrypto"
-version = "1.5.1"
-description = "Implementation of JOSE Web standards"
+name = "joserfc"
+version = "0.9.0"
+description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT"
 optional = false
-python-versions = ">= 3.6"
+python-versions = ">=3.8"
 files = [
-    {file = "jwcrypto-1.5.1.tar.gz", hash = "sha256:48bb9bf433777136253579e52b75ffe0f9a4a721d133d01f45a0b91ed5f4f1ae"},
+    {file = "joserfc-0.9.0-py3-none-any.whl", hash = "sha256:4026bdbe2c196cd40574e916fa1e28874d99649412edaab0e373dec3077153fb"},
+    {file = "joserfc-0.9.0.tar.gz", hash = "sha256:eebca7f587b1761ce43a98ffd5327f2b600b9aa5bb0a77b947687f503ad43bc0"},
 ]
 
 [package.dependencies]
-cryptography = ">=3.4"
-deprecated = "*"
+cryptography = "*"
+
+[package.extras]
+drafts = ["pycryptodome"]
 
 [[package]]
 name = "limits"
@@ -1550,7 +1553,6 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
-    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
     {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
     {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
     {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -2060,4 +2062,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.9"
-content-hash = "54e33e527f9f597c2775263a0801e4e74b4297353c402dff1ab67f4963a383bc"
+content-hash = "fd6a5238f1d7b0eb915b347c4f066a5ffc48a84457a51204a58869cf41607aa9"
diff --git a/pyproject.toml b/pyproject.toml
index c3736ba71919f66bf5edb243c9e4db08e4cbe76e..d21724de10582bb1890618a93f6f8e9ea212c5af 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,8 +50,8 @@ Werkzeug = "^3.0.1"
 email-validator = "^2.1.0.post1"
 pyotp = "^2.9.0"
 qrcode = "^7.4.2"
-jwcrypto = "^1.5.1"
 Pillow = "^10.2.0"
+joserfc = "^0.9.0"
 bootstrap-flask = "^2.3.3"
 
 [tool.poetry.group.dev]