A Python library for OpenID Connect (OIDC) authentication with DPoP (Demonstrating Proof-of-Possession) support, featuring JWT validation, token caching, and both sync/async operations.
- Table of Contents (ToC)
- Features
- Installation
- Quick Start
- Advanced Usage (Low-Level API)
- API Reference
- Advanced Configuration
- Development
- Contributing
- 🔐 OIDC Authentication - Full OpenID Connect authentication support
- 🔑 DPoP Support - Demonstrating Proof-of-Possession for enhanced security
- ✅ JWT Validation - Comprehensive token validation with JWKS
- 💾 Token Caching - Built-in memory cache for tokens and JWKS
- ⚡ Async/Sync - Supports both synchronous and asynchronous operations
- 🎯 Type Safe - Fully typed with Python type hints
- 🔒 Flexible Auth Methods -
client_secret_jwt,client_secret_post, andclient_secret_basicwith automatic fallback
uv add axa-fr-oidcpip install axa-fr-oidcThe OidcClient provides a simplified, high-level API for common OIDC operations:
from axa_fr_oidc import OidcClient
# Create a client with client credentials
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
scopes=["openid", "profile"],
audience="your-api-audience",
)
# Get an access token (automatically cached and refreshed)
access_token = client.get_access_token()
# Force a fresh token from the authorization server (bypasses cache)
fresh_token = client.get_access_token(force_renew_token=True)
# Validate a token
result = client.validate_token(access_token)
if result.success:
print(f"Token is valid! Subject: {result.payload['sub']}")
else:
print(f"Token is invalid: {result.error}")
# Clean up resources
client.close_sync()from axa_fr_oidc import OidcClient
# Sync context manager
with OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
) as client:
token = client.get_access_token()
# Async context manager
async with OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
) as client:
token = await client.get_access_token_async()import asyncio
from axa_fr_oidc import OidcClient
async def main():
async with OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
) as client:
# Async token retrieval
token = await client.get_access_token_async()
# Async token validation
result = await client.validate_token_async(token)
print(result.success, result.payload)
asyncio.run(main())from axa_fr_oidc import OidcClient
# Load your private key
with open("private_key.pem", "r") as f:
private_key_pem = f.read()
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
private_key=private_key_pem,
algorithm="RS256",
scopes=["openid", "profile"],
)
token = client.get_access_token()from axa_fr_oidc import OidcClient
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
)
# Validate a DPoP-bound token
result = client.validate_token(
token=access_token,
dpop=dpop_proof,
path="/api/resource",
http_method="POST",
)
print(result.success, result.error)from axa_fr_oidc import OidcClient
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
)
# Exchange a token (RFC 8693)
new_token = client.token_exchange(
subject_token=user_token,
requested_token_type="urn:ietf:params:oauth:token-type:access_token",
)The client supports custom HTTP configurations including proxy settings, SSL verification, and timeouts:
from axa_fr_oidc import OidcClient
# Using a proxy
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
proxy="http://proxy.example.com:8080",
)
# Using an HTTPS proxy
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
proxy="https://secure-proxy.example.com:8443",
)
# Disable SSL verification (not recommended for production)
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
verify=False,
)
# Set custom timeout (in seconds)
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
timeout=30.0,
)
# Combine multiple HTTP configurations
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
proxy="http://proxy.example.com:8080",
verify=True,
timeout=10.0,
)
token = client.get_access_token()When using client_secret, you can control how the credentials are sent to the
token endpoint via the auth_method parameter.
auth_method |
Behaviour |
|---|---|
"client_secret_jwt" (default) |
Signs an HS256 JWT assertion (RFC 7523). Automatically falls back to client_secret_post on 401 if the server does not have this method enabled for the client. |
"client_secret_post" |
Sends client_id + client_secret in the POST body. Broadly supported. |
"client_secret_basic" |
Sends credentials as an HTTP Basic Auth header. Broadly supported. |
from axa_fr_oidc import OidcClient
from axa_fr_oidc.constants import (
CLIENT_SECRET_AUTH_METHOD_JWT, # "client_secret_jwt" (default)
CLIENT_SECRET_AUTH_METHOD_POST, # "client_secret_post"
CLIENT_SECRET_AUTH_METHOD_BASIC, # "client_secret_basic"
)
# Default: tries client_secret_jwt, falls back to client_secret_post on 401
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
)
# Explicitly use client_secret_post (no fallback overhead)
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
auth_method=CLIENT_SECRET_AUTH_METHOD_POST,
)
# Explicitly use client_secret_basic
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
auth_method=CLIENT_SECRET_AUTH_METHOD_BASIC,
)
token = client.get_access_token()For more details, see the Client Secret Auth Methods Guide.
from axa_fr_oidc import JWTAuthorization
authorization_header = "<your-jwt-token>"
jwt_auth = JWTAuthorization(authorization_header)
print(jwt_auth.get_property("sub")) # Print the subject of the token
print(jwt_auth.get_property("exp")) # Print the expiration time of the tokenFor users who need more control over the authentication process, the library provides low-level components that can be customized individually.
from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
from httpx import AsyncClient, Client
# Create HTTP clients
http_client = Client()
http_async_client = AsyncClient()
# Create HTTP service
http_service = XHttpServiceGet(
http_client=http_client,
http_async_client=http_async_client
)
# Create cache
memory_cache = MemoryCache()
# Create authentication handler
auth = OidcAuthentication(
issuer="https://issuer.url",
scopes=["openid", "profile"],
api_audience="your-api-audience",
service=http_service,
memory_cache=memory_cache,
algorithms=["RS256", "ES256"],
)
# Create OpenID Connect client
oidc = OpenIdConnect(
authentication=auth,
memory_cache=memory_cache,
client_id="your-client-id",
client_secret="your-client-secret"
)
# Get access token
access_token = oidc.get_access_token()
# Validate token
result = auth.validate(access_token, None, None, None)
print(result.success, result.error)All low-level components support async/await:
from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
from httpx import AsyncClient, Client
async def main():
http_service = XHttpServiceGet(
http_client=Client(),
http_async_client=AsyncClient()
)
memory_cache = MemoryCache()
auth = OidcAuthentication(
issuer="https://issuer.url",
scopes=["openid", "profile"],
api_audience="your-api-audience",
service=http_service,
memory_cache=memory_cache
)
oidc = OpenIdConnect(
authentication=auth,
memory_cache=memory_cache,
client_id="your-client-id",
client_secret="your-client-secret"
)
# Async token retrieval
access_token = await oidc.get_access_token_async()
# Async token validation
result = await auth.validate_async(access_token, None, None, None)
print(result.success, result.payload)
# Run with asyncio
import asyncio
asyncio.run(main())For client credentials flow with private key (JWT bearer) using the low-level API:
from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
from httpx import AsyncClient, Client
# Load your private key
with open("private_key.pem", "r") as f:
private_key_pem = f.read()
http_service = XHttpServiceGet(
http_client=Client(),
http_async_client=AsyncClient()
)
memory_cache = MemoryCache()
auth = OidcAuthentication(
issuer="https://issuer.url",
scopes=["openid", "profile"],
api_audience="your-api-audience",
service=http_service,
memory_cache=memory_cache
)
oidc = OpenIdConnect(
authentication=auth,
memory_cache=memory_cache,
client_id="your-client-id",
private_key=private_key_pem,
algorithm="RS256" # or other supported algorithms
)
access_token = oidc.get_access_token()Note: For most use cases, consider using the simpler
OidcClientinstead. See the Quick Start section for examples.
You can customize various timeouts and cache settings:
from axa_fr_oidc import OidcClient
client = OidcClient(
issuer="https://issuer.url",
client_id="your-client-id",
client_secret="your-client-secret",
scopes=["openid", "profile"],
audience="your-api-audience",
algorithms=["RS256", "ES256"], # Allowed algorithms for validation
issuer_cache_expiration_seconds=7200, # Cache JWKS and token_endpoint for 2 hours (default: 3600)
)from axa_fr_oidc import (
OidcAuthentication,
MemoryCache,
XHttpServiceGet,
)
from httpx import AsyncClient, Client
auth = OidcAuthentication(
issuer="https://issuer.url",
scopes=["openid", "profile"],
api_audience="your-api-audience",
service=XHttpServiceGet(
http_client=Client(),
http_async_client=AsyncClient()
),
memory_cache=MemoryCache(),
algorithms=["RS256", "ES256"], # Supported algorithms
)OidcClient- Simplified, all-in-one client for OIDC operationsget_access_token(force_renew_token=False)/get_access_token_async(force_renew_token=False)- Get an access token (setforce_renew_token=Trueto bypass cache)validate_token()/validate_token_async()- Validate an access tokentoken_exchange()- Exchange tokens (RFC 8693)get_token_endpoint()/get_token_endpoint_async()- Get the token endpoint URLclear_cache()- Clear all cached dataclose()/close_sync()- Release resources- Supports context managers (
with/async with)
OidcAuthentication- OIDC token validation and JWKS managementOpenIdConnect- Client for obtaining access tokensMemoryCache- In-memory cache for tokens and JWKSXHttpServiceGet- HTTP service wrapper for sync/async requestsJWTAuthorization- Utility for extracting JWT claimsAuthenticationResult- Result object from validation operations
All main classes have corresponding interfaces for dependency injection:
IOidcAuthentication- Interface for OidcAuthenticationIOpenIdConnect- Interface for OpenIdConnectIMemoryCache- Interface for MemoryCacheIHttpServiceGet- Interface for XHttpServiceGetIGenericAuthorization- Interface for JWTAuthorization
The library exports useful constants for configuration:
from axa_fr_oidc import (
DEFAULT_DPOP_MAX_AGE_SECONDS, # 300 (5 minutes)
DEFAULT_CLOCK_SKEW_SECONDS, # 300 (5 minutes)
DEFAULT_JTI_LIFETIME_SECONDS, # 300 (5 minutes)
DEFAULT_JWT_ALGORITHM, # "RS256"
DEFAULT_JWT_EXPIRATION_SECONDS, # 300 (5 minutes)
DEFAULT_HTTP_TIMEOUT_SECONDS, # 5 seconds
SUPPORTED_ALGORITHMS, # ["RS256", "HS256"]
DPOP_TOKEN_TYPE, # "dpop+jwt"
GRANT_TYPE_CLIENT_CREDENTIALS, # "client_credentials"
CLIENT_ASSERTION_TYPE_JWT_BEARER, # "urn:ietf:params:oauth:..."
CONTENT_TYPE_FORM_URLENCODED, # "application/x-www-form-urlencoded"
OIDC_WELL_KNOWN_PATH, # "/.well-known/openid-configuration"
CLIENT_SECRET_AUTH_METHOD_JWT, # "client_secret_jwt"
CLIENT_SECRET_AUTH_METHOD_POST, # "client_secret_post"
CLIENT_SECRET_AUTH_METHOD_BASIC, # "client_secret_basic"
)For detailed information on configuring the client-secret auth method
(client_secret_jwt, client_secret_post, client_secret_basic) and the
automatic fallback behaviour, see the
Client Secret Auth Methods Guide.
For detailed information on configuring HTTP proxies, SSL verification, and timeouts, see the Proxy Configuration Guide.
Quick examples:
# Using a proxy
client = OidcClient(
issuer="https://auth.example.com",
client_id="your-client-id",
client_secret="your-client-secret",
proxy="http://proxy.example.com:8080",
)
# With custom timeout
client = OidcClient(
issuer="https://auth.example.com",
client_id="your-client-id",
client_secret="your-client-secret",
timeout=30.0,
)# Clone the repository
git clone https://github.com/your-org/axa-fr-oidc.git
cd axa-fr-oidc
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv sync --group devThe project includes a Makefile for convenient development commands:
# Show all available commands
make help
# Install dependencies
make install # Production dependencies only
make install-dev # All development dependencies
make install-quality # Quality check tools only
make install-test # Test dependencies only
# Code quality
make lint # Run ruff linter (includes docstring checks)
make lint-fix # Run ruff linter with auto-fix
make format # Run ruff formatter
make format-check # Check formatting without changes
make type-check # Run mypy type checker
# Security
make security # Run bandit security checks
make security-audit # Run pip-audit for dependency vulnerabilities
# Testing
make test # Run tests
make test-cov # Run tests with coverage report
# Combined commands
make quality # Run all quality checks (lint, format, type-check, security)
make all # Run quality checks and tests
# Cleanup
make clean # Remove build artifacts and cache files# Using make
make test
# Or directly with uv
uv run pytest# Run all quality checks at once
make quality
# Or run individual checks
make lint
make type-check
make security# Install only test dependencies
uv sync --group test
# Install only linting tools
uv sync --group lint
# Install only security tools
uv sync --group security
# Install everything for development
uv sync --group devContributions are welcome! Please feel free to submit a Pull Request.