Generic OIDC Provider
OidcProvider is a JWT authentication provider for any standards-compliant
OpenID Connect identity provider — Authentik,
Keycloak, Auth0, Okta, or any other issuer
that exposes a .well-known/openid-configuration discovery document and a
JWKS endpoint. Unlike AWS Cognito Provider and Azure Entra ID Authentication Provider,
it is not tied to a specific cloud vendor.
Overview
The OIDC provider handles:
JWKS discovery — the signing keys are fetched automatically from
{issuer}/.well-known/openid-configuration(or an explicitjwks_uri), and cached the same way as the other providers.Signature verification and validation of the
exp/nbfclaims (with configurable clock-skew leeway).Issuer validation (
iss) — always enforced.Audience validation (
aud) — enforced whenaudienceis configured. Strongly recommended: without it, any token signed by the issuer for any client of that issuer is accepted.Claim mapping — the claims used for the user’s display name and groups are configurable, since different providers name them differently (OIDC’s own convention is
preferred_usernameandgroups, which are also Authentik’s defaults).
Configuration
Basic Configuration
from auth_middleware import JwtAuthMiddleware
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.oidc.oidc_provider_settings import (
OidcProviderSettings,
)
# Configure the OIDC settings — this example targets an Authentik
# application, but the same settings work for any OIDC-compliant IdP.
auth_settings = OidcProviderSettings(
issuer="https://authentik.example.com/application/o/my-app/",
audience="your-oidc-client-id",
)
# Create the provider
auth_provider = OidcProvider(settings=auth_settings)
# Add to FastAPI application
app.add_middleware(JwtAuthMiddleware, auth_provider=auth_provider)
Warning
audience is optional but strongly recommended, for the same reason
as Cognito’s user_pool_client_id: without it, the provider only
checks that the token was signed by the configured issuer, not which
client it was issued for.
Explicit JWKS URL
By default the JWKS URL is discovered from the issuer’s OIDC discovery
document on first use, and cached afterwards. If your IdP doesn’t expose
standard discovery, or you want to skip that extra request, set
jwks_uri explicitly:
auth_settings = OidcProviderSettings(
issuer="https://authentik.example.com/application/o/my-app/",
audience="your-oidc-client-id",
jwks_uri="https://authentik.example.com/application/o/my-app/jwks/",
)
Claim Mapping
Different identity providers use different claim names for the user’s display name and group memberships. Configure them to match your IdP:
auth_settings = OidcProviderSettings(
issuer="https://idp.example.com/",
audience="your-client-id",
username_claim="upn", # default: "preferred_username"
groups_claim="roles", # default: "groups"
)
Set groups_claim=None to disable reading groups from the token claims
entirely — useful if groups should come from a separate
GroupsProvider instead
(e.g. Groups Provider backed by SQL).
Clock-Skew Leeway
If the server clocks of your application and identity provider can drift,
allow a small grace period when validating exp/nbf:
auth_settings = OidcProviderSettings(
issuer="https://idp.example.com/",
audience="your-client-id",
jwt_leeway=30, # seconds, default 0
)
Example: Authentik
See Authentik Infrastructure Setup for how to create the Authentik-side OAuth2/OIDC provider and application, and how to include group membership in the token.
from fastapi import FastAPI, Depends
from starlette.requests import Request
from auth_middleware import JwtAuthMiddleware
from auth_middleware.guards import require_user, require_groups
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.oidc.oidc_provider_settings import (
OidcProviderSettings,
)
app = FastAPI(title="Authentik Example API")
auth_settings = OidcProviderSettings(
issuer="https://authentik.example.com/application/o/my-app/",
audience="your-oidc-client-id",
groups_claim="groups", # see the Authentik setup guide to enable this
)
app.add_middleware(
JwtAuthMiddleware,
auth_provider=OidcProvider(settings=auth_settings),
)
@app.get("/profile", dependencies=[Depends(require_user())])
async def get_profile(request: Request):
user = request.state.current_user
return {"user": user.name, "email": user.email, "groups": await user.groups}
@app.get("/admin", dependencies=[Depends(require_groups(["admins"]))])
async def admin_only():
return {"message": "Admin access granted"}
Example: Keycloak
See Keycloak Infrastructure Setup for how to create the realm and client in Keycloak, and how to map group membership into the token.
from fastapi import FastAPI, Depends
from starlette.requests import Request
from auth_middleware import JwtAuthMiddleware
from auth_middleware.guards import require_user, require_groups
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.oidc.oidc_provider_settings import (
OidcProviderSettings,
)
app = FastAPI(title="Keycloak Example API")
auth_settings = OidcProviderSettings(
issuer="https://keycloak.example.com/realms/myapp",
audience="my-app",
groups_claim="groups", # see the Keycloak setup guide to enable this
)
app.add_middleware(
JwtAuthMiddleware,
auth_provider=OidcProvider(settings=auth_settings),
)
@app.get("/profile", dependencies=[Depends(require_user())])
async def get_profile(request: Request):
user = request.state.current_user
return {"user": user.name, "email": user.email, "groups": await user.groups}
@app.get("/admin", dependencies=[Depends(require_groups(["admins"]))])
async def admin_only():
return {"message": "Admin access granted"}
Building Your Own Provider
If your identity provider isn’t OIDC-compliant, or you need behavior
OidcProvider doesn’t cover, subclass the
JWTProvider contract
directly and implement load_jwks, verify_token, and
create_user_from_token — this is exactly how CognitoProvider,
EntraIDProvider, and OidcProvider itself are built. See
Extending Authorization Providers for the equivalent pattern on the
authorization side (groups/roles/permissions providers).
API Reference
Generic OpenID Connect JWT provider.
Works with any standards-compliant OIDC identity provider — Authentik,
Keycloak, Auth0, Okta, or any other issuer that exposes a
.well-known/openid-configuration discovery document and a JWKS
endpoint — instead of being tied to a specific cloud vendor.
- class auth_middleware.providers.oidc.oidc_provider.OidcProvider(settings: OidcProviderSettings, permissions_provider: PermissionsProvider | None = None, groups_provider: GroupsProvider | None = None, roles_provider: RolesProvider | None = None)[source]
Bases:
JWTProviderJWT authentication provider for any standards-compliant OIDC issuer.
Example
from auth_middleware.providers.oidc.oidc_provider import OidcProvider from auth_middleware.providers.oidc.oidc_provider_settings import ( OidcProviderSettings, ) settings = OidcProviderSettings( issuer="https://authentik.example.com/application/o/my-app/", audience="my-client-id", ) auth_provider = OidcProvider(settings=settings)
- __init__(settings: OidcProviderSettings, permissions_provider: PermissionsProvider | None = None, groups_provider: GroupsProvider | None = None, roles_provider: RolesProvider | None = None) None[source]
- async create_user_from_token(token: JWTAuthorizationCredentials) User[source]
Initializes a domain User object with data recovered from a JWT token issued by the configured OIDC provider.
- Parameters:
token (JWTAuthorizationCredentials) – the verified token.
- Returns:
Domain object.
- Return type:
User
- async get_keys() list[dict[str, Any]][source]
Fetch the JWKS keys from the OIDC identity provider.
- Returns:
a list of JWK keys
- Return type:
List[JWK]
- class auth_middleware.providers.oidc.oidc_provider_settings.OidcProviderSettings(_case_sensitive: bool | None = None, _nested_model_default_partial_update: bool | None = None, _env_prefix: str | None = None, _env_prefix_target: EnvPrefixTarget | None = None, _env_file: DotenvType | None = PosixPath('.'), _env_file_encoding: str | None = None, _env_ignore_empty: bool | None = None, _env_nested_delimiter: str | None = None, _env_nested_max_split: int | None = None, _env_parse_none_str: str | None = None, _env_parse_enums: bool | None = None, _cli_prog_name: str | None = None, _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, _cli_settings_source: CliSettingsSource[Any] | None = None, _cli_parse_none_str: str | None = None, _cli_hide_none_type: bool | None = None, _cli_avoid_json: bool | None = None, _cli_enforce_required: bool | None = None, _cli_use_class_docs_for_groups: bool | None = None, _cli_exit_on_error: bool | None = None, _cli_prefix: str | None = None, _cli_flag_prefix_char: str | None = None, _cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None, _cli_ignore_unknown_args: bool | None = None, _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None, _cli_shortcuts: Mapping[str, str | list[str]] | None = None, _secrets_dir: PathType | None = None, _build_sources: tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]] | None = None, *, jwt_secret_key: str | None = None, jwt_algorithm: str | None = 'HS256', jwt_token_verification_disabled: bool | None = False, jwt_leeway: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)] = 0, jwks_cache_strategy: ~typing.Literal['time', 'usage', 'both'] = 'both', jwks_background_refresh: bool = True, jwks_background_refresh_threshold: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.8, issuer: str, audience: str | None = None, jwks_uri: str | None = None, discovery_url: str | None = None, algorithms: list[str] = <factory>, username_claim: str = 'preferred_username', groups_claim: str | None = 'groups', jwks_cache_interval: int | None = 20, jwks_cache_usages: int | None = 1000)[source]
Bases:
JWTProviderSettingsSettings for a generic OpenID Connect provider.
Works with any standards-compliant OIDC identity provider (Authentik, Keycloak, Auth0, Okta, …): the JWKS is discovered from the issuer’s
.well-known/openid-configurationdocument unlessjwks_uriis set explicitly.- model_config = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': '.env', 'env_file_encoding': 'utf-8', 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': '', 'env_prefix_target': 'variable', 'extra': 'ignore', 'frozen': True, 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
For more information about setting up an identity provider or other authentication providers, see:
Authentik Infrastructure Setup - Authentik OAuth2/OIDC provider setup
Keycloak Infrastructure Setup - Keycloak realm and client setup
AWS Cognito Provider - AWS Cognito integration
Azure Entra ID Authentication Provider - Azure Entra ID integration