ASGI/WSGI middleware for Python web applications
Project description
webapp-middleware
[[TOC]]
Overview
This package currently implements the following middlwares for both FastAPI/ASGI and Flask/WSGI web application frameworks:
AuthMiddleware: authentication middleware with support for ELB/Cognito, ALB/mTLS, and SAML/trusted header schemes, with optional support for enforcement of AUP acceptance.LogMiddleware: intercept-based logging configuration providing colorization/formatting, JSON serialization, and moreTimingMiddleware: profiles timings for operations and optionally publishes metrics logs and/or Server-Timing header
Installation
This package is published to PyPI as dlmp-middleware. Install it into your virtualenv with the
options for the middlewares you intend to use:
pip install "dlmp-middleware[auth,log]"
Available options:
authlogtiming
Note: the distribution name is
dlmp-middleware, but the import name remainsmiddleware(e.g.from middleware import AuthMiddleware).
It can also be installed directly from source (ensure SSH credentials for your Git host are configured):
pip install "dlmp-middleware[auth,log] @ git+ssh://<git-host>/<namespace>/webapp-middleware.git@<version>"
Middleware Packages
LogMiddleware
Log Format
Logs can be configured to be serialized to JSON (recommended if the destination is Cloudwatch Logs):
is_production = APP_ENV == "production"
LogMiddleware(json=is_production)
Exempt Paths
Specific paths can be exempted from logging, either by exact match or by path prefix followed by an asterisk:
log_exempt_paths = (
"/health",
"/static/*",
)
LogMiddleware(exempt=log_exempt_paths)
Usage Outside of Middleware
The intercept logger can be invoked outside of the context of middleware, useful in cases where an application contains non-web services and logging configuration should be normalized
Invoke configure_logging at application start:
from middleware import configure_logging
configure_logging(json=True)
In application code, use the logging module as usual:
import logging
logging.getLogger(__name__).warn("...")
TimingMiddleware
Defaults
In a default configuration, the FastAPI or Flask middleware publishes a timing configuration with the following keys:
route: Total time taken to process all code for a given route (inclusive of more granular timings below)encode: Time taken for the web framework to encode the responserender: Time taken for the web framework to render / produce the response
Configuring Application-specific Timings
To add application-specific timings, the timings argument can be used.
Below are some examples of common external data sources and APIs.
SQLAlchemy
import sqlalchemy
timings={
"db_exec": (
sqlalchemy.engine.base.Engine.execute,
),
"db_fetch": (
sqlalchemy.engine.ResultProxy.fetchone,
sqlalchemy.engine.ResultProxy.fetchmany,
sqlalchemy.engine.ResultProxy.fetchall,
),
}
AWS (boto3)
import botocore
timings={
"aws": (
botocore.client.BaseClient._make_api_call,
),
}
Redis (redis-py)
import redis
timings={
"redis": (
redis.connection.Connection.send_command,
redis.connection.Connection.read_response,
),
}
AuthMiddleware
ELB / Cognito
In the default configuration with no middleware options provided, if the x-amzn-oidc-data HTTP header is present,
Cognito authentication provided via ELB will automatically be used. An alternate header name can be optionally
provided:
AuthMiddleware(
elb_header_name="custom-http-header-name", # HTTP header name containing Cognito JWT (default: x-amzn-oidc-data)
)
Mock User
For development/testing purposes, a mock user can be statically provided:
test_user = {
"username": "testuser",
"groups": ["group-1", "group-2"],
}
AuthMiddleware(mock_user=test_user)
API Gateway / mTLS Authentication
User context can be read from connections proxied through API Gateway with mTLS authentication configured.
Note: If mTLS authentication is configured and an HTTP connection is made with the relevant header present but a user session cookie is not present, an exception will NOT be raised, access will be granted, and the user context will be left unset. The trusted TLS header asserted by API Gateway only attests that a connecting device bears a valid TLS certificate. Applications implementing separate device + user level authentication in this manner are required to assert the presence of a user session in application logic accordingly.
AuthMiddleware(
tls_cookie_name="my-app-user-cookie", # HTTP cookie name bearing user session credentials
tls_cookie_secret="user-cookie-secret", # signing secret for TLS cookie
tls_header_name="x-amzn-mtls-identity", # HTTP header name to use for detection of TLS authentication
)
SAML / Trusted Proxy
For applications deployed behind a trusted authenticating proxy, an HTTP header can be specified
AuthMiddleware(
saml_user_header_name="x-saml-user", # HTTP header name containing SAML username
saml_groups_header_name="x-saml-groups", # HTTP header name containing SAML groups
)
Exempt Paths
Specific HTTP paths can be exempted from requiring authentication, either by exact match or by HTTP path prefix followed by an asterisk. Note that user context will not be present on the request context for exempted routes:
auth_exempt_paths = (
"/health",
"/version",
"/public/*",
)
AuthMiddleware(exempt=auth_exempt_paths)
AUP Enforcement
An aup argument can be provided to AuthMiddleware which verifies that users have accepted an acceptable use policy
prior to allowing access to the application. It detects the presence of a JSON web token (JWT) signed by service at
url with HMAC passphrase secret which attests that a user has read and signed the AUP document identified by
aup_id. If the cookie does not exist or the signature is invalid, the user is redirected to the AUP service to
obtain a valid cookie.
The debug and exempt options configured for AuthMiddleware also apply to AUP enforcement.
The JWT payload is a JSON object with the following values:
user: username as authenticated byAuthMiddleware; must match the current authenticated user (required)signatures: an object with required AUP ID as a key and document revision as value (required)exp: expiration timestamp in epoch seconds (optional)
Configuration
AupService(
aup_id="my-aup-v1", # ID associated with the application's policy (required)
cookie_name="clickthrough_signatures", # Cookie name (default: clickthrough_signatures)
exempt=("/path", "/prefix/*"), # HTTP paths or prefixes to exempt from AUP enforcement (default: None)
mock_verified=False, # Whether to mock successful verification (default: false)
path="/verify", # AUP service HTTP path for verification requests (default: /verify)
secret="secret-key", # AUP service HMAC secret used to sign JWT (required)
url="https://aup.example.com", # AUP service base URL (required)
)
Mock Acceptance
AUP verification can be bypassed for development/testing purposes:
aup_service = AupService(mock_verified=True, …)
AuthMiddleware(aup=aup_service, …)
Examples
FastAPI
from fastapi import FastAPI
from middleware import AuthMiddleware
app = FastAPI()
auth_options = dict(
aup=dict(
aup_id="app-name-aup",
secret="service-secret-key",
url="https://aup.example.com",
),
exempt=("/healthcheck", "/version"),
)
app.add_middleware(AuthMiddleware, **auth_options)
Flask
from flask import Flask
from middleware import AuthMiddleware
app = Flask(__name__)
auth_options = dict(
aup=dict(
aup_id="app-name-aup",
secret="service-secret-key",
url="https://aup.example.com",
),
exempt=("/healthcheck", "/version"),
)
app.wsgi_app = AuthMiddleware(app.wsgi_app, **auth_options)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dlmp_middleware-1.3.6.tar.gz.
File metadata
- Download URL: dlmp_middleware-1.3.6.tar.gz
- Upload date:
- Size: 112.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
748395ac6d92ee7da87927b07b22729dab41de073fef755d38042c5d21880524
|
|
| MD5 |
12f8a642fe03d3b8db6271b499bd2f53
|
|
| BLAKE2b-256 |
4cf8c9fd24563018e9a08750913397804b9e38e5d2cd3603678a7ecdf27d1135
|
File details
Details for the file dlmp_middleware-1.3.6-py3-none-any.whl.
File metadata
- Download URL: dlmp_middleware-1.3.6-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03f01e20713a11c02c64c386aa519bc7823f910560e9a6f7f67f2d41d3b3447a
|
|
| MD5 |
9454a7ceabee441c2d156046d41295c5
|
|
| BLAKE2b-256 |
cd4a923b7eb851665548f912b4b2f943db8411815702787a3bd3f22f487a0711
|