Repository: okta/okta-jwt-verifier-python
Issue category: Security Vulnerability

Issue:
Dependency python-Jose appears to be unmaintained
Hey - just a heads-up that it appears this library is using `python-jose` as a dependency, which hasn't been updated in ~2 years. Maintainers haven't shown any activity in GitHub for issues or pull requests in quite a while, either. It would probably be prudent to pivot to PyJWT or JWCrypto, before CVEs start cropping up against the abandoned library.
Dependency python-Jose appears to be unmaintained
Hey - just a heads-up that it appears this library is using `python-jose` as a dependency, which hasn't been updated in ~2 years. Maintainers haven't shown any activity in GitHub for issues or pull requests in quite a while, either. It would probably be prudent to pivot to PyJWT or JWCrypto, before CVEs start cropping up against the abandoned library.


Hints:
> before CVEs start dropping up against the abandoned library.

Looks like that's now, found in [python-ecdsa](https://github.com/tlsfuzzer/python-ecdsa/security/advisories/GHSA-wj6h-64fc-37mp).

Are there any plans to use the `cryptography` build of `python-jose`, or migrate?
@bretterer any updates on this?  Does Okta have a migration plan?  Or should we switch to a new library altogether? 
Yet more vulnerabilities discovered in `python-jose`. 

We could _really_ do with a PyJWT build of this 
> before CVEs start dropping up against the abandoned library.

Looks like that's now, found in [python-ecdsa](https://github.com/tlsfuzzer/python-ecdsa/security/advisories/GHSA-wj6h-64fc-37mp).

Are there any plans to use the `cryptography` build of `python-jose`, or migrate?
@bretterer any updates on this?  Does Okta have a migration plan?  Or should we switch to a new library altogether? 
Yet more vulnerabilities discovered in `python-jose`. 

We could _really_ do with a PyJWT build of this 

Patch:
diff --git a/okta_jwt_verifier/jwt_utils.py b/okta_jwt_verifier/jwt_utils.py
index af187ed..dd1e3d1 100644
--- a/okta_jwt_verifier/jwt_utils.py
+++ b/okta_jwt_verifier/jwt_utils.py
@@ -1,7 +1,8 @@
 import json
 
-from jose import jwt, jws
-from jose.exceptions import ExpiredSignatureError
+import jwt
+from jwt.exceptions import ExpiredSignatureError
+
 
 from .constants import LEEWAY
 from .exceptions import JWTValidationException
@@ -17,7 +18,8 @@ def parse_token(token):
         Return:
             tuple (headers, claims, signing_input, signature)
         """
-        headers, payload, signing_input, signature = jws._load(token)
+        jws_api = jwt.api_jws.PyJWS()
+        payload, signing_input, headers, signature = jws_api._load(token)
         claims = json.loads(payload.decode('utf-8'))
         return (headers, claims, signing_input, signature)
 
@@ -28,7 +30,8 @@ def verify_claims(claims,
                       issuer,
                       leeway=LEEWAY):
         """Verify claims are present and valid."""
-        # Check if required claims are present, because library "jose" doesn't raise an exception
+        # Check if required claims are present
+        # This may not be required with the `pyjwt` implementation.
         for claim in claims_to_verify:
             if claim not in claims:
                 raise JWTValidationException(f'Required claim "{claim}" is not present.')
@@ -41,21 +44,23 @@ def verify_claims(claims,
                    'verify_iss': 'iss' in claims_to_verify,
                    'verify_sub': 'sub' in claims_to_verify,
                    'verify_jti': 'jti' in claims_to_verify,
-                   'leeway': leeway}
+                   'require': claims_to_verify,
+                   'leeway': leeway,
+                   'verify_signature': False,}
         # Validate claims
-        jwt._validate_claims(claims,
-                             audience=audience,
-                             issuer=issuer,
-                             options=options)
+        jwt_api = jwt.api_jwt.PyJWT()
+        jwt_api._validate_claims(payload=claims, options=options, audience=audience, issuer=issuer, leeway=leeway)
 
     @staticmethod
     def verify_signature(token, okta_jwk):
         """Verify token signature using received jwk."""
         headers, claims, signing_input, signature = JWTUtils.parse_token(token)
-        jws._verify_signature(signing_input=signing_input,
+        parsed_jwk = jwt.PyJWK(okta_jwk)
+        jws_api = jwt.api_jws.PyJWS()
+        jws_api._verify_signature(signing_input=signing_input,
                               header=headers,
                               signature=signature,
-                              key=okta_jwk,
+                              key=parsed_jwk.key,
                               algorithms=['RS256'])
 
     @staticmethod
diff --git a/requirements.txt b/requirements.txt
index 29aec61..6318d7c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 requests>=2.31.0
-python-jose>=3.2.0
+pyjwt>=2.8.0
 acachecontrol>=0.3.5
 retry2
 aiohttp>=3.9.2


Test patch:
diff --git a/tests/unit/test_jwt_verifier.py b/tests/unit/test_jwt_verifier.py
index 3693b63..a3d8680 100644
--- a/tests/unit/test_jwt_verifier.py
+++ b/tests/unit/test_jwt_verifier.py
@@ -1,7 +1,7 @@
 import pytest
 import time
 
-from jose.exceptions import JWTClaimsError
+from jwt.exceptions import InvalidTokenError
 
 from okta_jwt_verifier import BaseJWTVerifier, JWTVerifier, AccessTokenVerifier, IDTokenVerifier
 from okta_jwt_verifier.exceptions import JWKException, JWTValidationException
@@ -101,7 +101,7 @@ def test_verify_signature(mocker):
     mocker.patch('okta_jwt_verifier.jwt_utils.JWTUtils.parse_token', mock_parse_token)
 
     mock_sign_verifier = mocker.Mock()
-    mocker.patch('okta_jwt_verifier.jwt_utils.jws._verify_signature',
+    mocker.patch('okta_jwt_verifier.jwt_utils.jwt.api_jws.PyJWS._verify_signature',
                  mock_sign_verifier)
 
     token = 'test_token'
@@ -184,7 +184,7 @@ def test_verify_claims_invalid():
               'sub': 'test_jwt@okta.com'}
     # verify when aud is a string
     jwt_verifier = BaseJWTVerifier(issuer, client_id)
-    with pytest.raises(JWTClaimsError):
+    with pytest.raises(InvalidTokenError):
         jwt_verifier.verify_claims(claims, ('iss', 'aud', 'exp'))