Repository navigation
Conversation
…explicit and removes the deprecation warning for the authlib.jose library.Signed
064981e to
ff4c63f
Compare
|
@kishore7snehil Can we get a review of this please? it's an irritating warning... |
|
@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.
Done - there weren't any conflicts when I put this up... |
|
@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.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| audience="my-audience", | ||
| jwt_algorithms=["RS384", "RS512"], | ||
| )) | ||
| assert client._jwt_algorithms == ["RS384", "RS512"] |
There was a problem hiding this comment.
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.
| token = jwt.encode(header, proof_claims, key) | ||
| else: | ||
| key = jwk.import_key(PRIVATE_EC_JWK) | ||
| registry = JWSRegistry( |
There was a problem hiding this comment.
This registry exists only so tests can set odd headers, and it lives in the installed package.
|
|
||
| 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. |
There was a problem hiding this comment.
This comment joins two clauses with a semicolon.
| 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"]). |
There was a problem hiding this comment.
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.
|
|
||
| - 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. |
There was a problem hiding this comment.
The docstring says "default RS256" without naming the option.
| ApiClient(ApiClientOptions( | ||
| domain="example.us.auth0.com", | ||
| audience="my-audience", | ||
| jwt_algorithms=[], |
There was a problem hiding this comment.
There are no bad-input cases here (a tuple, a string, [None]), unlike the parameterized cases in test_utils.py.
|
@blochs Reviewed the changes and added few comments. PTAL! |
Changes
Replaced deprecated
authlib.joseusage with Authlib’s transitivejoserfcdependency.This removes the
AuthlibDeprecationWarningemitted by Authlib 1.7+ while preserving existing JWT, JWK, and DPoP behavior.Additional changes:
ApiClientOptionsnow acceptsjwt_algorithms, defaulting to["RS256"].ES256.joserfc’s stricter header validation.Example:
Alternative approaches considered:
joserfcas a direct dependency, but it remains transitive as requested.References
Testing
Reviewers can run:
Result:
210 passed.The test suite verifies JWT verification, DPoP verification, malformed DPoP headers, configurable algorithms, and existing behavior.
Checklist