Skip to content

chore: Resolves Issue #92, updates to use transitive dependency explicit - #134

Open
blochs wants to merge 14 commits into
auth0:mainfrom
blochs:deprecated-lib-warning
Open

blochs wants to merge 14 commits into
auth0:mainfrom
blochs:deprecated-lib-warning

Conversation

@blochs

@blochs blochs commented Sep 29, 2026 •

Copy link
Copy Markdown

Changes

Replaced deprecated authlib.jose usage with Authlib’s transitive joserfc dependency.

This removes the AuthlibDeprecationWarning emitted by Authlib 1.7+ while preserving existing JWT, JWK, and DPoP behavior.

Additional changes:

  • No endpoints were added, deleted, deprecated, or changed.
  • ApiClientOptions now accepts jwt_algorithms, defaulting to ["RS256"].
  • Access-token verification supports configurable signing algorithms.
  • DPoP verification remains restricted to ES256.
  • Updated JWT test utilities to work with joserfc’s stricter header validation.
  • No UI changes or screenshots are applicable.

Example:

ApiClientOptions(
    domain="example.us.auth0.com",
    audience="my-api",
    jwt_algorithms=["RS384", "RS512"],
)

Alternative approaches considered:

  • Pinning Authlib below 1.7, which would only postpone the deprecation.
  • Adding joserfc as a direct dependency, but it remains transitive as requested.
  • Suppressing the warning, which would leave deprecated API usage in place.

References

Testing

  • This change adds unit test coverage
  • This change adds integration test coverage
  • This change has been tested on Python 3.13.7

Reviewers can run:

PYTHONPATH=src pytest tests/test_api_client.py tests/test_utils.py -q

Result: 210 passed.

The test suite verifies JWT verification, DPoP verification, malformed DPoP headers, configurable algorithms, and existing behavior.

Checklist

@blochs
blochs requested a review from a team as a code owner September 29, 2026 12:46
…explicit

and removes the deprecation warning for the authlib.jose library.Signed
@blochs
blochs force-pushed the deprecated-lib-warning branch from 064981e to ff4c63f Compare September 29, 2026 12:52
@blochs

blochs commented Oct 29, 2026

Copy link
Copy Markdown
Author

@kishore7snehil Can we get a review of this please? it's an irritating warning...

@kishore7snehil

Copy link
Copy Markdown
Contributor

@blochs Can you please resolve the conflicts so I can take a look?

Preserve upstream token exchange, organization policy, security validations,
and test coverage while retaining JWT algorithm configuration, joserfc
access-token and DPoP verification, and matching regression coverage.
@blochs

blochs commented Oct 29, 2026

Copy link
Copy Markdown
Author

@blochs Can you please resolve the conflicts so I can take a look?

Done - there weren't any conflicts when I put this up...

@kishore7snehil

Copy link
Copy Markdown
Contributor

@blochs UTs are failing. Can you please check?

Use Authlib 1.6 JOSE on Python 3.9, where joserfc 1.7+ is not
available through Authlib. Keep the non-deprecated joserfc JWT/JWK
implementation on Python 3.10+ and preserve algorithm restrictions.

Fixes PR auth0#134 Build and Test import failure on Python 3.9.
@blochs

blochs commented Oct 29, 2026

Copy link
Copy Markdown
Author

@blochs UTs are failing. Can you please check?

It's 3.9 as the issue, which I'm pretty sure is EOL.

if sys.version_info < (3, 10):
from authlib.jose import JsonWebKey, JsonWebToken
else:
from joserfc import jwk, jwt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

joserfc is imported directly here but isn't listed in pyproject.toml or requirements.txt. It only arrives through authlib 1.7 and above, and authlib = "^1.0" still allows older versions that don't bring it. On Python 3.10+ with one of those, importing the package would fail.

Should we add joserfc as a dependency for 3.10+, or raise the authlib minimum? WDYT?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3.9 is well past EOL. I'd raise the minimum?

if sys.version_info < (3, 10):
claims = self._dpop_jwt.decode(proof, public_key)
else:
claims = jwt.decode(proof, public_key, algorithms=self._dpop_algorithms).claims

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On 3.10+ the decoded proof is used as a dict straight away, but joserfc doesn't check that the payload is a JSON object. A proof with a payload of 5, null, true or a plain string gets past decode and then fails in the claims check with a TypeError (for example "argument of type 'int' is not iterable"). Before this change those cases raised InvalidDpopProofError. verify_request only catches InvalidDpopProofError here, so callers would see the TypeError.

Shall we check the result is a dict right after decode and raise InvalidDpopProofError if not? A test with a non-object payload would help too, next to the existing non-object header tests.

Comment thread src/auth0_api_python/api_client.py Outdated
raise MissingRequiredArgumentError("audience")

if not isinstance(options.jwt_algorithms, list) or not options.jwt_algorithms or not all(
isinstance(algorithm, str) and algorithm for algorithm in options.jwt_algorithms

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jwt_algorithms accepts any non-empty strings, so ["none"] or ["RS256", "none"] is allowed. With that config a token with alg: none and no signature passes verification on both the joserfc and Authlib paths (I tried 3.9 and 3.10). The default ["RS256"] still rejects it, so this only happens if someone opts in.

Can we reject none when the option is validated? WDYT?

if sys.version_info < (3, 10):
claims = self._jwt.decode(access_token, public_key)
else:
claims = jwt.decode(access_token, public_key, algorithms=self._jwt_algorithms).claims

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With joserfc, a JWKS key that has its own alg has to match the token's alg. So jwt_algorithms=["RS384", "RS512"] (the example in the new test) fails with unsupported_key_alg when the key says alg: RS256. The same setup works on 3.9 with Authlib, so the option behaves differently by Python version. I haven't tried this against a real tenant's JWKS.

Is this intended? If so, it needs a mention in the docs.

Comment thread tests/test_api_client.py
audience="my-audience",
jwt_algorithms=["RS384", "RS512"],
))
assert client._jwt_algorithms == ["RS384", "RS512"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only checks the private _jwt_algorithms attribute, so it doesn't show the option is used when a token is verified. I replaced the decode calls with a hardcoded ["RS256"] on both Python paths and all 470 tests still passed. Dropping the list-type check also left everything green.

Can we add a test that signs a token with RS384 and checks it's accepted only when configured and rejected with the default? generate_token hardcodes RS256, so it would need an alg option.

Comment thread src/auth0_api_python/token_utils.py Outdated
token = jwt.encode(header, proof_claims, key)
else:
key = jwk.import_key(PRIVATE_EC_JWK)
registry = JWSRegistry(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This registry exists only so tests can set odd headers, and it lives in the installed package.

Comment thread src/auth0_api_python/token_utils.py Outdated

from .utils import calculate_jwk_thumbprint, normalize_url_for_htu, sha256_base64url

# Python 3.9 resolves to Authlib 1.6 (without joserfc); use its JOSE API there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment joins two clauses with a semicolon.

Comment thread src/auth0_api_python/config.py Outdated
Can be a static list of domain strings or a callable that returns
allowed domains dynamically. Optional if domain is provided.
audience: The expected 'aud' claim in the token.
jwt_algorithms: Allowed access-token signing algorithms (default: ["RS256"]).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entry sits after audience, but the parameter is the last one in the signature. It also doesn't say that an empty list raises ConfigurationError.

Comment thread src/auth0_api_python/api_client.py Outdated

- Fetches OIDC metadata and JWKS if not already cached.
- Decodes and validates signature (RS256) with the correct key.
- Decodes and validates signature (allowed algorithms, default RS256) with the correct key.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says "default RS256" without naming the option.

Comment thread tests/test_api_client.py Outdated
ApiClient(ApiClientOptions(
domain="example.us.auth0.com",
audience="my-audience",
jwt_algorithms=[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no bad-input cases here (a tuple, a string, [None]), unlike the parameterized cases in test_utils.py.

@kishore7snehil

kishore7snehil commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

@blochs Reviewed the changes and added few comments. PTAL!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants