Service Authorization¶
TiTiler OpenEO implements a flexible service authorization mechanism that controls access to the served instance of a secondary web service (the XYZ/WMTS/WMS tile endpoint published as the service's url). Each service can be configured with different access levels for that instance through the scope parameter.
Important scope of this feature: scope governs only the tile-serving endpoint (GET /services/xyz/{service_id}/tiles/{z}/{x}/{y}, i.e. what service.url points to). It does not apply to GET /services/{service_id} or any other /services* management endpoint — those always require Bearer authentication, matching the openEO spec exactly (security: [Bearer: []], with no anonymous variant, unlike GET /service_types). This distinction between the always-private control plane (/services/{service_id}) and the back-end-defined data plane (service.url) is intentional in the spec, not an oversight — see ADR 0003 for the full writeup, including an earlier, incorrect attempt to make the metadata endpoint follow scope as well (reverted).
Note on the openEO spec: the openEO API specification does not define any access-control property for secondary web services at all — configuration.scope is entirely a TiTiler OpenEO extension governing only how titiler-openeo happens to serve tiles. Whether this is worth proposing upstream, and if so in what form, is an open question currently being discussed with the openEO maintainers; see ADR 0003 for the current status.
Scopes¶
Services can be configured with one of three scopes:
private: Only the service owner can fetch tiles from the servicerestricted: Any authenticated user can fetch tiles, with optional user-specific restrictionspublic(current default — see the note below): No authentication required to fetch tiles
Note on the default: the current default is public, which contradicts the "use private by default" guidance in Best Practices below. This is a known inconsistency, tracked in ADR 0003; flipping the default is a deployment-visible behaviour change and will ship as an explicit, settings-controlled opt-in rather than silently.
Configuration¶
Authorization is configured through the service configuration object when creating or updating a service:
{
"configuration": {
"scope": "restricted",
"authorized_users": ["user1", "user2"] // Optional: specific users for restricted scope
}
}
Configuration Parameters¶
| Parameter | Type | Description |
|---|---|---|
scope |
string | Access scope: private, restricted, or public |
authorized_users |
array | Optional list of user IDs allowed to access a restricted service |
Implementation¶
The authorization mechanism is implemented in two main components:
ServiceAuthorizationManagerclass (titiler/openeo/services/auth.py):- Encapsulates authorization logic
- Validates access based on service configuration and user context
-
Throws appropriate HTTP exceptions for unauthorized access
-
Service endpoints:
- Retrieve service configuration
- Use ServiceAuthorizationManager to enforce access control
- Pass authorized requests to the service implementation
Example Usage¶
For example:
The behavior of the injected user parameter depends on how it's defined in the process's JSON schema:
- When the parameter schema defines
"type": "string":
The process will receive just the user ID string, even when using from_parameter:
{
"process_graph": {
"example1": {
"process_id": "example_process",
"arguments": {
"user_id": {
"from_parameter": "_openeo_user" // Will extract just the user_id
}
}
}
}
}
- When the parameter schema defines a User object type:
{
"parameters": {
"user": {
"type": "object",
"description": "User object with full properties"
}
}
}
The process will receive the complete User object:
{
"process_graph": {
"example1": {
"process_id": "example_process",
"arguments": {
"user": {
"from_parameter": "_openeo_user" // Will provide the full User object
}
}
}
}
}
from titiler.openeo.services.auth import ServiceAuthorizationManager
# In your service endpoint:
service = services_store.get_service(service_id)
auth_manager = ServiceAuthorizationManager()
auth_manager.authorize(service, user) # Raises HTTPException if access denied
Authorization Flow¶
- Client requests a service endpoint
- Service configuration is retrieved from the store
- ServiceAuthorizationManager validates access based on:
- Service scope
- User authentication status
- User authorization (for restricted services)
- If access is denied:
- 401 Unauthorized - For missing authentication
- 403 Forbidden - For insufficient permissions
- If access is granted, the request proceeds to service execution
User Injection¶
If the service call is authenticated, the authenticated user will be injected into the process graph as a named parameter _openeo_user. Thus any process graph parameter can reference the authenticated user by using from_parameter: "_openeo_user".
Best Practices¶
- Always set an appropriate scope for your services
- Use
privatescope by default for maximum security - For restricted services, explicitly list authorized users
- Consider using
publicscope only for non-sensitive data - Regularly audit service configurations and access patterns