AuthVerifier Singleton Initialization#
Auth0AuthVerifier and its siblings are instantiated once per route via FastAPI's Depends() / Security() dependency injection. Each route calls IdentityManagerFactory.get_auth_verifier(scopes) , which dynamically imports the appropriate verifier module and calls its constructor with the route's required scopes. This means a fresh Auth0AuthVerifier object is created for every route handler that declares one as a dependency.
OIDC Discovery: Where It Happens#
auth0_authverifier.py performs OIDC discovery in two places, both of which run more often than necessary:
-
Module import time β a
_discover_jwks_uri()helper is called at the top level to initialize the module-leveljwks_client. This call hitshttps://{AUTH0_DOMAIN}/.well-known/openid-configurationviarequests.getthe first time the module is loaded. BecauseIdentityManagerFactoryusesimportlib.import_moduleon everyget_auth_verifiercall , any test fixture that reloads the containing API module can re-trigger this network call outside a mock context. -
Per-constructor β
Auth0AuthVerifier.__init__also calls_discover_jwks_uri(), re-running the OIDC discovery HTTP request on every new verifier instance. The comment in the constructor explicitly acknowledges this:"TODO: this verifier should be instantiated once and not for every endpoint/route to better cache the jwks keys"
The module-level jwks_client uses cache_keys=True so that individual JWKS key lookups are cached within a single process lifetime , but that caching is undermined when the class is re-instantiated per route because the in-constructor discovery call still fires.
Why This Matters#
- Redundant network calls: Every route that requires auth causes a new OIDC discovery HTTP call, adding latency at startup and during dependency resolution.
- Test timeouts: The import-time discovery call caused real
AUTH_TYPE=AUTH0tests to hang withTimeout >20s, because thetest_appfixture's module reload cascaded into re-importing the auth verifier outside any mock context. This was patched by wrapping the module reload in apatch("requests.get")context intests/fixtures/client.py. - Keycloak / non-Auth0 providers: The original code hardcoded the JWKS URI as
/.well-known/jwks.json, which is Auth0-specific and breaks Keycloak. PR #5593 fixed this by adding_discover_jwks_uri()to follow the standard OIDC discovery spec β but in doing so, it introduced the per-constructor discovery call that compounds the singleton problem.
Current Architecture#
| Component | File | Role |
|---|---|---|
AuthVerifierBase | keep/identitymanager/authverifierbase.py | Abstract base; FastAPI __call__ entry point for auth |
Auth0AuthVerifier | ee/identitymanager/identity_managers/auth0/auth0_authverifier.py | Concrete verifier; performs OIDC discovery + JWT validation |
IdentityManagerFactory | keep/identitymanager/identitymanagerfactory.py | Factory that instantiates a verifier per route via get_auth_verifier(scopes) |
Other verifiers (NoAuthVerifier , DbAuthVerifier , Oauth2proxyAuthVerifier ) do not perform OIDC discovery; the singleton concern is specific to Auth0AuthVerifier.
Open Issue: Singleton Refactor#
The TODO in Auth0AuthVerifier.__init__ signals the intended fix: make Auth0AuthVerifier (or at minimum its jwks_client and the OIDC discovery result) a module-level or application-level singleton so that discovery fires only once per process. The jwks_client already exists at module scope with cache_keys=True; the remaining work is to eliminate the redundant per-instance _discover_jwks_uri() call in __init__ and ensure the factory returns the same instance across routes (or at least re-uses the cached discovery result).