Warning
This project is currently in active development and may change drastically in the near future while we work towards solidifying a first release.
STAC Auth Proxy is a proxy API that mediates between the client and an internally accessible STAC API to provide a flexible authentication, authorization, and content-filtering mechanism.
- 🔐 Authentication: Selectively apply OIDC auth to some or all endpoints & methods
- 🎟️ Content Filtering: Apply CQL2 filters to client requests, filtering API content based on user context
- 📖 OpenAPI Augmentation: Update OpenAPI with security requirements, keeping auto-generated docs/UIs accurate (e.g. Swagger UI)
Note
Currently, the project can only be installed by downloading the repository. It will eventually be available on Docker (#5) and PyPi (#30).
For local development, we use uv
to manage project dependencies and environment.
uv sync
Otherwise, the application can be installed as a standard Python module:
pip install -e .
The simplest way to run the project is by calling the module directly:
python -m stac_auth_proxy
Alternatively, the application's factory can be passed to Uvicorn:
uvicorn --factory stac_auth_proxy:create_app
The application is configurable via environment variables.
UPSTREAM_URL
- The STAC API to proxy requests to
- Type: HTTP(S) URL
- Required: Yes
- Example:
https://your-stac-api.com/stac
OIDC_DISCOVERY_URL
- OpenID Connect discovery document URL
- Type: HTTP(S) URL
- Required: Yes
- Example:
https://auth.example.com/.well-known/openid-configuration
OIDC_DISCOVERY_INTERNAL_URL
- The internal network OpenID Connect discovery document URL
- Type: HTTP(S) URL
- Required: No, defaults to the value of
OIDC_DISCOVERY_URL
- Example:
http://auth/.well-known/openid-configuration
DEFAULT_PUBLIC
- Description: Default access policy for endpoints
- Type: boolean
- Default:
false
- Example:
false
,1
,True
PRIVATE_ENDPOINTS
- Description: Endpoints explicitly marked as requiring authentication, for use when
DEFAULT_PUBLIC == True
- Type: JSON object mapping regex patterns to HTTP methods OR tuples of HTTP methods and an array of strings representing required scopes
- Default:
{ "^/collections$": ["POST"], "^/collections/([^/]+)$": ["PUT", "PATCH", "DELETE"], "^/collections/([^/]+)/items$": ["POST"], "^/collections/([^/]+)/items/([^/]+)$": ["PUT", "PATCH", "DELETE"], "^/collections/([^/]+)/bulk_items$": ["POST"] }
- Description: Endpoints explicitly marked as requiring authentication, for use when
PUBLIC_ENDPOINTS
- Description: Endpoints explicitly marked as not requiring authentication, for use when
DEFAULT_PUBLIC == False
- Type: JSON object mapping regex patterns to HTTP methods
- Default:
{ "^/api.html$": ["GET"], "^/api$": ["GET"] }
- Description: Endpoints explicitly marked as not requiring authentication, for use when
OPENAPI_SPEC_ENDPOINT
- Path to serve OpenAPI specification
- Type: string or null
- Default:
null
(disabled) - Example:
/api
ITEMS_FILTER
- Configuration for item-level filtering
- Type: JSON object with class configuration
- Default:
null
- Components:
cls
: Python import pathargs
: List of positional argumentskwargs
: Dictionary of keyword arguments
- Example:
{ "cls": "my_package.filters.OrganizationFilter", "args": ["org1"], "kwargs": { "field_name": "properties.organization" } }
ITEMS_FILTER_ENDPOINTS
- Where to apply item filtering
- Type: JSON object mapping regex patterns to HTTP methods
- Default:
{ "^/search$": ["POST"], "^/collections/([^/]+)/items$": ["GET", "POST"] }
While this project aims to provide utility out-of-the-box as a runnable application, it's likely won't address every project's needs. In these situations, this codebase can instead be treated as a library of components that can be used to augment any webserver that makes use of the ASGI protocol (e.g. Django, Falcon, FastAPI, Litestar, Responder, Sanic, Starlette). Review app.py
to get a sense of how we make use of the various components to construct a FastAPI application.
The middleware stack is processed in reverse order (bottom to top):
-
EnforceAuthMiddleware
- Handles authentication and authorization
- Configurable public/private endpoints
- OIDC integration
- Places auth token payload in request state
-
BuildCql2FilterMiddleware
- Builds CQL2 filters based on request context/state
- Places CQL2 expression in request state
-
ApplyCql2FilterMiddleware
- Retrieves CQL2 expression from request state
- Augments request with CQL2 filter:
- Modifies query strings for GET requests
- Modifies JSON bodies for POST/PUT/PATCH requests
-
OpenApiMiddleware
- Modifies OpenAPI specification based on endpoint configuration, adding security requirements
- Only active if
openapi_spec_endpoint
is configured
-
AddProcessTimeHeaderMiddleware
- Adds processing time headers
- Useful for monitoring/debugging
The system supports generating CQL2 filters based on request context to provide row-level content filtering. These CQL2 filters are then set on outgoing requests prior to the upstream API.
Important
The upstream STAC API must support the STAC API Filter Extension, including the Features Filter conformance class on to the Features resource (/collections/{cid}/items
) #37.
Tip
Integration with external authorization systems (e.g. Open Policy Agent) can be achieved by replacing the default BuildCql2FilterMiddleware
with a custom async middleware that is capable of generating cql2.Expr
objects.
sequenceDiagram
Client->>Proxy: GET /collections
Note over Proxy: EnforceAuth checks credentials
Note over Proxy: BuildCql2Filter creates filter immediately
Note over Proxy: ApplyCql2Filter modifies query string
Proxy->>STAC API: GET /collection?filter=(collection=landsat)
STAC API->>Client: Response
Supported | Method | Endpoint | Action | Filter | Strategy |
---|---|---|---|---|---|
✅ | POST |
/search |
Read | Item | Append body with generated CQL2 query. |
✅ | GET |
/search |
Read | Item | Append query params with generated CQL2 query. |
❌ (#22) | POST |
/collections/ |
Create | Collection | Validate body with generated CQL2 query. |
❌ (#23) | GET |
/collections/{collection_id} |
Read | Collection | Append query params with generated CQL2 query. |
❌ (#22) | PUT |
/collections/{collection_id}} |
Update | Collection | Fetch Collection and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
❌ (#22) | DELETE |
/collections/{collection_id} |
Delete | Collection | Fetch Collectiion and validate with CQL2 query. |
✅ | GET |
/collections/{collection_id}/items |
Read | Item | Append query params with generated CQL2 query. |
❌ (#25) | GET |
/collections/{collection_id}/items/{item_id} |
Read | Item | Validate response against CQL2 query. |
❌ (#21) | POST |
/collections/{collection_id}/items |
Create | Item | Validate body with generated CQL2 query. |
❌ (#21) | PUT |
/collections/{collection_id}/items/{item_id} |
Update | Item | Fetch Item and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
❌ (#21) | DELETE |
/collections/{collection_id}/items/{item_id} |
Delete | Item | Fetch Item and validate with CQL2 query. |
❌ (#21) | POST |
/collections/{collection_id}/bulk_items |
Create | Item | Validate items in body with generated CQL2 query. |