Auth Middleware
Async Authentication and Authorization Middleware for FastAPI/Starlette Applications
Auth Middleware is a comprehensive, production-ready authentication and authorization solution for FastAPI and Starlette applications. It provides a pluggable architecture that supports multiple identity providers and authorization backends with async-first design principles.
Features
Authentication Providers
- Generic OIDC — Any standards-compliant OpenID Connect provider — AWS Cognito, Authentik, Keycloak, Auth0, Okta, ... — via
OidcProvider - Azure Entra ID — Microsoft Azure Active Directory authentication
- Basic Auth — Username/password authentication via
BasicAuthMiddleware - Custom Providers — Extensible architecture via the
JWTProvidercontract
Authorization & Access Control
- Group-based Authorization — Role-based access control with user groups
- Role-based Authorization — Fine-grained role system
- Permission-based Authorization — Fine-grained permission system
- SQL Backend Support — PostgreSQL and MySQL for groups/permissions storage via SQLAlchemy
- Cognito Groups Integration — Direct integration with AWS Cognito groups (
cognito:groupsclaim) - Cognito Groups-as-Roles — Map Cognito groups directly to roles
- Custom Authorization Providers — Build your own via the
GroupsProvider,RolesProviderandPermissionsProvidercontracts
Performance & Reliability
- Async-First Design — Built for high-performance async applications
- JWKS Caching — Intelligent caching of JSON Web Key Sets
- Connection Pooling — Efficient database and HTTP connections
- Lazy Loading — User groups and permissions loaded on-demand
- Error Resilience — Graceful degradation on provider failures
Developer Experience
- Type-Safe — Full type hints throughout, compatible with mypy strict mode
- FastAPI Integration — Native dependency injection support
- Middleware Pattern — Standard ASGI middleware implementation
- Environment Configuration — 12-factor app configuration support
- Comprehensive Documentation — Detailed guides and API reference
Installation
# pip
pip install auth-middleware
# poetry
poetry add auth-middleware
# uv (recommended)
uv add auth-middleware
Quick Start
JWT Authentication with AWS Cognito (via generic OIDC)
from fastapi import FastAPI, Depends, 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()
auth_settings = OidcProviderSettings(
issuer="https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abcdef123",
audience="your-app-client-id", # recommended: rejects tokens issued for other clients
jwt_token_verification_disabled=False,
)
app.add_middleware(
JwtAuthMiddleware,
auth_provider=OidcProvider(settings=auth_settings),
)
# Requires valid authentication
@app.get("/protected", dependencies=[Depends(require_user())])
async def protected_endpoint(request: Request):
user = request.state.current_user
return {"message": f"Hello {user.name}", "user_id": user.id}
# Requires group membership
@app.get("/admin", dependencies=[Depends(require_groups(["admin", "moderator"]))])
async def admin_endpoint(request: Request):
return {"message": "Admin access granted"}
Azure Entra ID
Set the required environment variables (read at module load time):
AUTH_PROVIDER_AZURE_ENTRA_ID_TENANT_ID=your-tenant-id
AUTH_PROVIDER_AZURE_ENTRA_ID_AUDIENCE_ID=your-app-client-id
from auth_middleware import JwtAuthMiddleware
from auth_middleware.providers.azure.entra_id_provider import EntraIDProvider
app.add_middleware(
JwtAuthMiddleware,
auth_provider=EntraIDProvider(),
)
Generic OIDC (Authentik, Keycloak, Auth0, Okta, ...)
Works with any standards-compliant OpenID Connect identity provider — the JWKS is discovered automatically from the issuer's .well-known/openid-configuration document.
from auth_middleware import JwtAuthMiddleware
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.oidc.oidc_provider_settings import OidcProviderSettings
auth_settings = OidcProviderSettings(
issuer="https://authentik.example.com/application/o/my-app/",
audience="your-oidc-client-id", # recommended: rejects tokens from other clients
)
app.add_middleware(
JwtAuthMiddleware,
auth_provider=OidcProvider(settings=auth_settings),
)
Basic Auth
from auth_middleware import BasicAuthMiddleware, hash_password
from auth_middleware.contracts import CredentialsRepository
from auth_middleware.types.user_credentials import UserCredentials
# When creating a user, hash their password once and store the result
# (e.g. hashed_password column) — never store the plaintext password.
stored_hash = hash_password("their-password")
class MyCredentialsRepository(CredentialsRepository):
async def get_by_id(self, *, id: str) -> UserCredentials | None:
# Look up the user and return their stored credentials, including
# the hash produced by hash_password() above
return UserCredentials(id=id, name="Jane Doe", hashed_password=stored_hash)
app.add_middleware(
BasicAuthMiddleware,
credentials_repository=MyCredentialsRepository(),
)
Configuration
Environment Variables
# Core middleware — always read from environment
AUTH_MIDDLEWARE_DISABLED=false
AUTH_MIDDLEWARE_LOG_LEVEL=INFO
# Azure Entra ID — always read from environment at module load (required when using EntraIDProvider)
AUTH_PROVIDER_AZURE_ENTRA_ID_TENANT_ID=your-tenant-id
AUTH_PROVIDER_AZURE_ENTRA_ID_AUDIENCE_ID=your-client-id
# Generic OIDC (e.g. AWS Cognito) — optional; alternative to passing values programmatically to OidcProviderSettings
# OIDC_ISSUER=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abcdef123
# OIDC_AUDIENCE=your-app-client-id
# JWKS_CACHE_INTERVAL=20 # minutes, default 20
# JWKS_CACHE_USAGES=1000 # verifications before refresh, default 1000
SQL-backed Groups and Permissions
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.sqlalchemy.sql_groups_provider import SqlGroupsProvider
from auth_middleware.providers.sqlalchemy.sql_permissions_provider import SqlPermissionsProvider
from auth_middleware.providers.sqlalchemy.async_database import AsyncDatabase
from auth_middleware.providers.sqlalchemy.async_database_settings import AsyncDatabaseSettings
AsyncDatabase.configure(AsyncDatabaseSettings(
database_url="postgresql+asyncpg://user:pass@localhost/mydb"
))
auth_provider = OidcProvider(
settings=auth_settings,
groups_provider=SqlGroupsProvider(),
permissions_provider=SqlPermissionsProvider(),
)
app.add_middleware(JwtAuthMiddleware, auth_provider=auth_provider)
Cognito Groups Provider
from auth_middleware.providers.oidc.oidc_provider import OidcProvider
from auth_middleware.providers.aws.cognito_groups_provider import CognitoGroupsProvider
# Groups extracted directly from the cognito:groups JWT claim
auth_provider = OidcProvider(
settings=auth_settings,
groups_provider=CognitoGroupsProvider(),
)
Usage Examples
Accessing User Information
@app.get("/profile", dependencies=[Depends(require_user())])
async def get_profile(request: Request):
user = request.state.current_user
return {
"id": user.id,
"name": user.name,
"email": user.email,
"groups": await user.groups,
"permissions": await user.permissions,
}
Role and Permission Guards
from auth_middleware.guards import require_roles, require_permissions
@app.get("/reports", dependencies=[Depends(require_roles(["analyst", "manager"]))])
async def get_reports(request: Request):
return {"message": "Reports access granted"}
@app.post("/admin/users", dependencies=[Depends(require_permissions(["user.create"]))])
async def create_user(request: Request):
return {"message": "User creation allowed"}
curl
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
http://localhost:8000/protected
Package Structure
auth_middleware/
├── jwt_auth_middleware.py # JwtAuthMiddleware (ASGI)
├── basic_auth_middleware.py # BasicAuthMiddleware (ASGI)
├── constants.py # AUTH_SCHEME_BASIC, AUTH_SCHEME_BEARER
├── contracts/ # Abstract base classes
│ ├── jwt_provider.py # JWTProvider
│ ├── groups_provider.py # GroupsProvider
│ ├── roles_provider.py # RolesProvider
│ ├── permissions_provider.py # PermissionsProvider
│ ├── profile_provider.py # ProfileProvider
│ └── credentials_repository.py # CredentialsRepository
├── guards/ # FastAPI dependency guards
│ ├── functions.py # require_user, require_groups, require_roles,
│ │ # require_permissions, get_current_user
│ ├── group_checker.py # GroupChecker
│ ├── role_checker.py # RoleChecker
│ └── permissions_checker.py # PermissionsChecker
├── providers/
│ ├── aws/ # AWS Cognito groups/roles + auth service (pair with OidcProvider)
│ │ ├── cognito_groups_provider.py
│ │ ├── cognito_groups_as_roles_provider.py
│ │ ├── cognito_profile_provider.py
│ │ └── services/ # Cognito auth service, M2M detector
│ ├── azure/ # Azure Entra ID
│ │ ├── entra_id_provider.py
│ │ └── settings.py
│ ├── oidc/ # Generic OIDC (AWS Cognito, Authentik, Keycloak, Auth0, ...)
│ │ ├── oidc_provider.py
│ │ └── oidc_provider_settings.py
│ └── sqlalchemy/ # SQL-backed authorization
│ ├── sql_groups_provider.py
│ ├── sql_permissions_provider.py
│ ├── async_database.py
│ └── async_database_settings.py
└── exceptions/ # InvalidTokenException, AuthenticationError, …
Architecture
graph TD
A[HTTP Request] --> B[JwtAuthMiddleware\nor BasicAuthMiddleware]
B --> C[JWTBearerManager]
C --> D[JWTProvider\nOidcProvider · EntraIdProvider]
D --> E[Token Validation\nJWKS cache + exp/iss/aud]
E --> F[User object]
F --> G[GroupsProvider\nCognitoGroupsProvider · SqlGroupsProvider]
F --> H[PermissionsProvider\nSqlPermissionsProvider]
F --> I[RolesProvider\nCognitoGroupsAsRolesProvider]
G --> J[request.state.current_user]
H --> J
I --> J
J --> K[Guards\nrequire_user · require_groups\nrequire_roles · require_permissions]
Core Components
| Component | Import path | Description |
|---|---|---|
JwtAuthMiddleware |
auth_middleware |
ASGI middleware for JWT authentication |
BasicAuthMiddleware |
auth_middleware |
ASGI middleware for Basic Auth |
JWTProvider |
auth_middleware.contracts |
Abstract base for JWT providers |
GroupsProvider |
auth_middleware.contracts |
Abstract base for group authorization |
RolesProvider |
auth_middleware.contracts |
Abstract base for role authorization |
PermissionsProvider |
auth_middleware.contracts |
Abstract base for permission authorization |
CredentialsRepository |
auth_middleware.contracts |
Abstract base for Basic Auth credential lookup |
require_user |
auth_middleware.guards |
Guard: requires authenticated user |
require_groups |
auth_middleware.guards |
Guard: requires group membership |
require_roles |
auth_middleware.guards |
Guard: requires role membership |
require_permissions |
auth_middleware.guards |
Guard: requires specific permissions |
get_current_user |
auth_middleware.guards |
Dependency: returns current user or None |
CognitoGroupsProvider |
auth_middleware.providers.aws.cognito_groups_provider |
Groups from cognito:groups claim |
CognitoGroupsAsRolesProvider |
auth_middleware.providers.aws.cognito_groups_as_roles_provider |
Cognito groups mapped as roles |
EntraIDProvider |
auth_middleware.providers.azure.entra_id_provider |
Azure Entra ID JWT provider |
OidcProvider |
auth_middleware.providers.oidc.oidc_provider |
Generic OIDC JWT provider (AWS Cognito, Authentik, Keycloak, Auth0, ...) |
SqlGroupsProvider |
auth_middleware.providers.sqlalchemy.sql_groups_provider |
DB-backed groups |
SqlPermissionsProvider |
auth_middleware.providers.sqlalchemy.sql_permissions_provider |
DB-backed permissions |
Authenticated User Properties
Available on request.state.current_user:
| Property | Type | Description |
|---|---|---|
id |
str |
Unique identifier from the identity provider |
name |
str | None |
Display name |
email |
EmailStr | None |
Email address |
groups |
list[str] (async) |
User groups |
roles |
list[str] (async) |
User roles |
permissions |
list[str] (async) |
User permissions |
Development
# Clone and set up
git clone https://github.com/impalah/auth-middleware.git
cd auth-middleware
make venv
# Quality checks
make test # run test suite
make type-check # mypy strict
make lint # ruff
make check # all checks at once
# Build & publish
make build
make publish
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT — see LICENSE for details.
Links
- Documentation: https://impalah.github.io/auth-middleware/
- PyPI Package: https://pypi.org/project/auth-middleware/
- Source Code: https://github.com/impalah/auth-middleware
- Bug Reports: GitHub Issues
Created by impalah — Made for modern Python async applications
Metadata
Release files for auth-middleware 0.6.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| auth_middleware-0.6.2.tar.gz | 9.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| auth_middleware-0.6.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 9.1 MB
Release files / auth_middleware-0.6.2.tar.gz
| Download URL | auth_middleware-0.6.2.tar.gz |
|---|---|
| Size | 9.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
286b01a790078e13892bacf3df2e3e78e15176c1aff87d29ba7ad47c2b0f706f
|
|
BLAKE2b-256 checksum How to use checksums |
1cf176ea19664b7d5a76a6137be2493596e561833e37915c488feecc75cf0a78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / auth_middleware-0.6.2-py3-none-any.whl
| Download URL | auth_middleware-0.6.2-py3-none-any.whl |
|---|---|
| Size | 71.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
915eba9017b34b2ff556927d7d3bc205fd0e82d9580d0a76dfbfe5907f4c9f78
|
|
BLAKE2b-256 checksum How to use checksums |
e182762d878ca3435b15d070279d89e81e92ae56a43b2090c24ebeb7939e16bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|