From c7fca34ba333b3a30fcaa9a69d6126881ed547b0 Mon Sep 17 00:00:00 2001 From: noahpodgurski Date: Thu, 11 Dec 2025 11:00:05 -0500 Subject: [PATCH 1/5] feat: migrate to jwtoxide, uptick version to 0.8.2 --- CHANGES.txt | 5 +++++ fxa/__init__.py | 2 +- fxa/oauth.py | 23 ++++++++++++++++++++--- pyproject.toml | 1 + 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 914a5dc..342e7bc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,11 @@ CHANGELOG This document describes changes between each past release. +0.8.2 (2025-12-11) +================== + +- Migrate to use jwtoxide + 0.8.1 (2025-05-01) ================== diff --git a/fxa/__init__.py b/fxa/__init__.py index 9e4ca1f..c65c877 100644 --- a/fxa/__init__.py +++ b/fxa/__init__.py @@ -7,7 +7,7 @@ """ -__version__ = "0.8.1" +__version__ = "0.8.2" __ver_tuple__ = tuple(__version__.split(".")) diff --git a/fxa/oauth.py b/fxa/oauth.py index bb0aa18..af9a079 100644 --- a/fxa/oauth.py +++ b/fxa/oauth.py @@ -9,6 +9,7 @@ from urllib.parse import urlparse, urlunparse, urlencode, parse_qs import jwt +from jwtoxide import DecodingKey, Jwk, ValidationOptions, decode from fxa.cache import MemoryCache, DEFAULT_CACHE_EXPIRY from fxa.constants import PRODUCTION_URLS from fxa.errors import OutOfProtocolError, ScopeMismatchError, TrustError @@ -204,9 +205,25 @@ def _verify_jwt_token(self, key, token): # which tokens. So there's no value in checking it here, and in fact if # we check it here, it fails because the right audience isn't being # requested. - decoded = jwt.decode( - token, pubkey, algorithms=['RS256'], options={'verify_aud': False} - ) + try: + # Try to first decode with jwtoxide + decoded = decode( + token, + DecodingKey.from_jwk(Jwk.from_json(key)), + ValidationOptions( + aud=None, + iss=None, + required_spec_claims={"iat", "exp"}, + validate_aud=False, + algorithms=["RS256"], + ), + ) + except: + # If something goes wrong, fallback to PyJWT + pubkey = jwt.algorithms.RSAAlgorithm.from_jwk(key) + decoded = jwt.decode( + token, pubkey, algorithms=["RS256"], options={"verify_aud": False} + ) # Ref https://tools.ietf.org/html/rfc7515#section-4.1.9 the `typ` header # is lowercase and has an implicit default `application/` prefix. typ = jwt.get_unverified_header(token).get('typ', '') diff --git a/pyproject.toml b/pyproject.toml index ab792a9..268b154 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dynamic = [ "version" ] dependencies = [ "cryptography", "hawkauthlib", + "jwtoxide", "pyjwt", "requests>=2.4.2", ] From ba08ef9bdefe0b35d33e52a299d6ce038ea7be3a Mon Sep 17 00:00:00 2001 From: noahpodgurski Date: Thu, 11 Dec 2025 11:12:58 -0500 Subject: [PATCH 2/5] add jwtoxide version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 268b154..21faab5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dynamic = [ "version" ] dependencies = [ "cryptography", "hawkauthlib", - "jwtoxide", + "jwtoxide==0.2.0", "pyjwt", "requests>=2.4.2", ] From 304777619893eb9131b6498ab08c2f621007a60c Mon Sep 17 00:00:00 2001 From: Noah Podgurski <42069075+noahpodgurski@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:22:37 -0500 Subject: [PATCH 3/5] Update fxa/oauth.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- fxa/oauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fxa/oauth.py b/fxa/oauth.py index af9a079..ddd91db 100644 --- a/fxa/oauth.py +++ b/fxa/oauth.py @@ -218,7 +218,7 @@ def _verify_jwt_token(self, key, token): algorithms=["RS256"], ), ) - except: + except Exception: # If something goes wrong, fallback to PyJWT pubkey = jwt.algorithms.RSAAlgorithm.from_jwk(key) decoded = jwt.decode( From 29baaf449b0ecebcd30541aec6c30d3378ed8988 Mon Sep 17 00:00:00 2001 From: noahpodgurski Date: Thu, 11 Dec 2025 12:55:30 -0500 Subject: [PATCH 4/5] rm duplicated line --- fxa/oauth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fxa/oauth.py b/fxa/oauth.py index ddd91db..d2b7d3d 100644 --- a/fxa/oauth.py +++ b/fxa/oauth.py @@ -199,7 +199,6 @@ def authorize_token(self, session, scope=None, client_id=None): return resp['access_token'] def _verify_jwt_token(self, key, token): - pubkey = jwt.algorithms.RSAAlgorithm.from_jwk(key) # The FxA OAuth ecosystem currently doesn't make good use of aud, and # instead relies on scope for restricting which services can accept # which tokens. So there's no value in checking it here, and in fact if From cc1ad9e78706d2467228556c290403b88adef518 Mon Sep 17 00:00:00 2001 From: noahpodgurski Date: Fri, 12 Dec 2025 08:39:07 -0500 Subject: [PATCH 5/5] remove python 3.8 support --- .github/workflows/test.yml | 2 +- CHANGES.txt | 3 ++- fxa/__init__.py | 2 +- pyproject.toml | 5 ++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1e70e1..f5a359c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Check out code uses: actions/checkout@v3 diff --git a/CHANGES.txt b/CHANGES.txt index 342e7bc..e8bb0c6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,10 +3,11 @@ CHANGELOG This document describes changes between each past release. -0.8.2 (2025-12-11) +0.9.0 (2025-12-11) ================== - Migrate to use jwtoxide +- Remove Python 3.8 support 0.8.1 (2025-05-01) ================== diff --git a/fxa/__init__.py b/fxa/__init__.py index c65c877..5c904e6 100644 --- a/fxa/__init__.py +++ b/fxa/__init__.py @@ -7,7 +7,7 @@ """ -__version__ = "0.8.2" +__version__ = "0.9.0" __ver_tuple__ = tuple(__version__.split(".")) diff --git a/pyproject.toml b/pyproject.toml index 21faab5..7bb8d7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,13 +16,12 @@ license = "MPL-2.0" authors = [ { name = "Mozilla Services", email = "services-dev@mozilla.org" }, ] -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -72,7 +71,7 @@ cov = "pytest --cov-config=pyproject.toml --cov=fxa/ --cov-report term-missing { [[tool.hatch.envs.test.matrix]] # Note: When changing these, also update the .github/workflows/test.yml file. -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12"] [tool.flake8] max-line-length = 99