cc: @eadwinCode
Summary
When NINJA_JWT["JWK_URL"] is set, django-ninja-jwt validates it as Pydantic v2 AnyUrl and passes that object into TokenBackend, which calls PyJWKClient(jwk_url) without casting to str. PyJWT then fails during URL parsing.
This appears to be a regression introduced by the Pydantic v2 settings migration in v5.4.2 (#156, fixing #154).
Environment
django-ninja-jwt: 5.4.4 (also reproducible on master as of 2026-05-26)
pydantic: 2.x
pyjwt: 2.x
- Python: 3.14
Reproduction
from pydantic import AnyUrl
from jwt import PyJWKClient
url = AnyUrl("https://example.com/.well-known/jwks.json")
PyJWKClient(url) # AttributeError: 'AnyUrl' object has no attribute 'decode'
With library settings:
- Configure Django
NINJA_JWT with "JWK_URL": "https://example.com/.well-known/jwks.json" and "ALGORITHM": "RS256".
- Import
ninja_jwt.state (module-level token_backend is built at import).
- Observe failure when
PyJWKClient is constructed with api_settings.JWK_URL (an AnyUrl, not a str).
Root cause
ninja_jwt/settings.py: JWK_URL: Optional[AnyUrl]
ninja_jwt/state.py: TokenBackend(..., api_settings.JWK_URL, ...)
ninja_jwt/backends.py: PyJWKClient(jwk_url) with no str() cast
Existing tests in tests/test_backends.py pass a plain string into TokenBackend(...) and do not cover the api_settings.JWK_URL → state.token_backend path.
Suggested fix
Either:
# ninja_jwt/state.py
token_backend = TokenBackend(
...
str(api_settings.JWK_URL) if api_settings.JWK_URL else None,
...
)
or cast inside TokenBackend.__init__ before PyJWKClient(jwk_url).
Suggested regression test
Build token_backend from a Django NINJA_JWT dict containing JWK_URL (integration path), reload ninja_jwt.state if needed, and assert token_backend.jwks_client is created without error.
Related
cc: @eadwinCode
Summary
When
NINJA_JWT["JWK_URL"]is set,django-ninja-jwtvalidates it as Pydantic v2AnyUrland passes that object intoTokenBackend, which callsPyJWKClient(jwk_url)without casting tostr. PyJWT then fails during URL parsing.This appears to be a regression introduced by the Pydantic v2 settings migration in v5.4.2 (#156, fixing #154).
Environment
django-ninja-jwt: 5.4.4 (also reproducible onmasteras of 2026-05-26)pydantic: 2.xpyjwt: 2.xReproduction
With library settings:
NINJA_JWTwith"JWK_URL": "https://example.com/.well-known/jwks.json"and"ALGORITHM": "RS256".ninja_jwt.state(module-leveltoken_backendis built at import).PyJWKClientis constructed withapi_settings.JWK_URL(anAnyUrl, not astr).Root cause
ninja_jwt/settings.py:JWK_URL: Optional[AnyUrl]ninja_jwt/state.py:TokenBackend(..., api_settings.JWK_URL, ...)ninja_jwt/backends.py:PyJWKClient(jwk_url)with nostr()castExisting tests in
tests/test_backends.pypass a plain string intoTokenBackend(...)and do not cover theapi_settings.JWK_URL→state.token_backendpath.Suggested fix
Either:
or cast inside
TokenBackend.__init__beforePyJWKClient(jwk_url).Suggested regression test
Build
token_backendfrom a DjangoNINJA_JWTdict containingJWK_URL(integration path), reloadninja_jwt.stateif needed, and asserttoken_backend.jwks_clientis created without error.Related