PyJWT Key Fetcher
PyJWT Key Fetcher is an async Python library designed to fetch JSON Web Key Sets (JWKS) for JWT token verification. It automatically retrieves issuer configurations (e.g., from OpenID Connect discovery endpoints) to locate JWKS URIs and fetch the correct public keys. This library acts as an improved async replacement for `PyJWKClient` from PyJWT. The current version is 0.8.0, and it maintains a relatively active release cadence with several updates per year.
Common errors
-
AttributeError: 'AsyncKeyFetcher' object has no attribute 'get_openid_configuration'
cause Attempting to call a method that was renamed in version 0.3.0.fixThe method `get_openid_configuration` was renamed to `get_configuration` in v0.3.0. Use `await fetcher.get_configuration(token)` instead. -
TypeError: object of type 'AsyncKeyFetcher' is not awaitable
cause Forgetting to use `await` with an asynchronous method or directly instantiating an async class without `async with` or `await` where expected.fixEnsure all calls to `AsyncKeyFetcher` methods (like `get_key`, `get_configuration`) are prefixed with `await`. Also, `AsyncKeyFetcher` itself is not awaitable; you instantiate it directly and then call its async methods. -
jwt.exceptions.InvalidAudienceError: Invalid audience
cause The audience claim in the JWT does not match the `audience` parameter provided to `jwt.decode()`, or no `audience` was provided.fixEnsure the `audience` parameter passed to `jwt.decode()` exactly matches the `aud` claim in the JWT. For production, never set `verify_aud=False`. -
jwt.exceptions.InvalidIssuerError: Invalid issuer
cause The issuer claim in the JWT does not match the `issuer` parameter provided to `jwt.decode()`, or no `issuer` was provided.fixEnsure the `issuer` parameter passed to `jwt.decode()` exactly matches the `iss` claim in the JWT. For production, never set `verify_iss=False`.
Warnings
- breaking Major refactoring in v0.3.0 renamed all explicit OpenID Connect references to be more generic. This includes `get_openid_configuration` to `get_configuration`, `OpenIDProvider` to `Provider`, and `JWTOpenIDConnectError` exceptions.
- breaking The type definition `OpenIDConfigurationTypeDef` was removed in v0.8.0. You should now use `ConfigurationTypeDef` instead.
- gotcha The `AsyncKeyFetcher` uses caching with a default TTL (Time To Live) of 3600 seconds (1 hour) for JWKS data. This means key revocations might not take effect immediately, as the old key could remain in the cache. New `kid` values will trigger a re-fetch if not seen in 5 minutes.
- gotcha While `pyjwt-key-fetcher` retrieves the correct signing key, `jwt.decode()` from PyJWT still requires explicit `audience` and `issuer` parameters for full validation, especially when dealing with OIDC tokens. Failing to provide these can lead to tokens being accepted that shouldn't be.
Install
-
pip install pyjwt-key-fetcher
Imports
- AsyncKeyFetcher
from pyjwt_key_fetcher import AsyncKeyFetcher
- Provider
from pyjwt_key_fetcher.openid_provider import OpenIDProvider
from pyjwt_key_fetcher.provider import Provider
Quickstart
import asyncio
import jwt
from pyjwt_key_fetcher import AsyncKeyFetcher
async def main():
# Example token from PyJWT documentation for demonstration
# In a real app, this would come from an Authorization header
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
fetcher = AsyncKeyFetcher(valid_issuers=["https://dev-87evx9ru.auth0.com/"])
try:
key_entry = await fetcher.get_key(token)
# The fetched key_entry can then be used with PyJWT's decode function
decoded_token = jwt.decode(
jwt=token,
options={"verify_exp": False}, # Set to True for production
audience="https://expenses-api",
issuer="https://dev-87evx9ru.auth0.com/",
**key_entry
)
print("Successfully decoded token:", decoded_token)
except Exception as e:
print(f"Error decoding token: {e}")
if __name__ == "__main__":
asyncio.run(main())