OpenID Connect Configuration¶
TiTiler-OpenEO supports OpenID Connect (OIDC) authentication following the OpenEO authentication model. The implementation supports the OpenID Connect Authorization Code Flow with PKCE.
The implementation is available in titiler/openeo/auth.py with the main class being OIDCAuth.
OpenEO Authentication Model¶
TiTiler-OpenEO follows the OpenEO authentication specification where tokens are provided in the format:
The token structure consists of three parts:
- Authentication method (
oidc) - Provider identifier (
oidc) - The actual OIDC token
Token parsing is handled by the AuthToken class.
Configuration¶
The OIDC configuration is managed through OIDCConfig in the settings. To enable OpenID Connect authentication, configure the following environment variables:
TITILER_OPENEO_AUTH_METHOD=oidc
TITILER_OPENEO_AUTH_OIDC_CLIENT_ID="your-client-id"
TITILER_OPENEO_AUTH_OIDC_WK_URL="https://your-provider/.well-known/openid-configuration"
TITILER_OPENEO_AUTH_OIDC_REDIRECT_URL="your-redirect-url"
Optional configuration:
TITILER_OPENEO_AUTH_OIDC_SCOPES="openid email profile" # Space-separated list (default)
TITILER_OPENEO_AUTH_OIDC_NAME_CLAIM="name" # Claim to use for user name (default)
TITILER_OPENEO_AUTH_OIDC_TITLE="OIDC" # Provider title (default)
TITILER_OPENEO_AUTH_OIDC_DESCRIPTION="OpenID Connect (OIDC) Authorization Code Flow with PKCE" # Provider description (default)
TITILER_OPENEO_AUTH_OIDC_AUDIENCES="" # Space-separated extra accepted `aud` values
TITILER_OPENEO_AUTH_OIDC_USER_ID_CLAIM="sub" # Claim used as User.user_id (default)
TITILER_OPENEO_AUTH_METHOD=oidc is validated at startup: the backend refuses
to start if CLIENT_ID or WK_URL is unset, rather than failing on the first
request.
USER_ID_CLAIM is not safe to change on a running deployment
Services, UDPs and tile assignments are all keyed on User.user_id.
Changing which claim produces it orphans everything already stored.
Microsoft Entra ID¶
Microsoft Entra ID works with the configuration above, with one requirement.
Use a single-tenant well-known URL. The multi-tenant common endpoint
returns a templated issuer, https://login.microsoftonline.com/{tenantid}/v2.0,
which is a literal placeholder rather than a URL. It cannot be compared against
a token's iss and cannot be advertised to openEO clients, so the backend
rejects it with a message naming the fix.
TITILER_OPENEO_AUTH_METHOD=oidc
TITILER_OPENEO_AUTH_OIDC_CLIENT_ID="<application (client) ID>"
TITILER_OPENEO_AUTH_OIDC_WK_URL="https://login.microsoftonline.com/<tenant_id>/v2.0/.well-known/openid-configuration"
TITILER_OPENEO_AUTH_OIDC_REDIRECT_URL="http://localhost:8080/"
TITILER_OPENEO_AUTH_OIDC_NAME_CLAIM="preferred_username"
TITILER_OPENEO_AUTH_OIDC_TITLE="Microsoft Entra ID"
Expose an API scope, and mint the token out-of-band¶
openEO clients send the access token, never the ID token. With only
openid profile email requested, Entra issues an access token audienced at
Microsoft Graph, and those cannot be validated by anyone but Graph: they
carry a nonce in the JWT header and their signature will not verify against
the tenant's published JWKS. The symptom is a 401 with "Token signature
verification failed".
Entra must therefore issue a token audienced at this backend:
- App registration → Expose an API → Set the Application ID URI →
accept the default
api://<client_id>. - Add a scope, e.g.
openeo, with admin and user consent enabled. - Manifest → set
"requestedAccessTokenVersion": 2. Without this Entra issues a v1 access token whoseissishttps://sts.windows.net/<tenant>/, which will not match the v2 discovery document's issuer and fails theisscheck with "Invalid issuer".
The openEO Python client drops this scope by default
openeo (checked at 0.48.0) intersects the scopes a backend advertises with
the provider's own scopes_supported
(openeo/rest/auth/oidc.py, OidcProviderInfo.__init__):
{"openid"}.union(scopes).intersection(self._supported_scopes). Microsoft
Entra advertises a fixed ["openid", "profile", "email", "offline_access"]
— static platform metadata that by construction never lists an app's custom
API scopes — so api://<client_id>/openeo is silently dropped and Entra
falls back to a Graph-audienced token. scopes_supported is RECOMMENDED
and informational per RFC 8414,
so this client-side filtering is arguably too strict. Microsoft has
confirmed
that Entra will never advertise custom scopes, so this cannot be fixed from
the provider side.
Undo the intersection with the helper this project ships. Call it once, before connecting — from any notebook or script:
from titiler.openeo.client_compat import patch_openeo_client_scopes
patch_openeo_client_scopes()
import openeo
connection = openeo.connect("http://127.0.0.1:8083/").authenticate_oidc()
The call is idempotent, so re-running a cell is harmless, and importing the
module patches nothing on its own. _scopes is computed once in
OidcProviderInfo.__init__, so this covers every flow — authorization code,
device code and refresh token all read it through get_scopes_string.
With it in place TITILER_OPENEO_AUTH_OIDC_SCOPES works as documented and the
ordinary authenticate_oidc() flow succeeds, keeping refresh-token caching.
Patch at runtime rather than editing the installed package — uv sync reverts a
site-packages edit.
The upstream report is drafted at
docs/adr/upstream/openeo-python-client-scope-intersection.md; drop the helper
once a release carrying a fix is the minimum this project supports.
If you would rather not patch a third-party class, mint the token out-of-band
with MSAL and hand it over with authenticate_oidc_access_token()
(openeo >= 0.31); this also needs no redirect URI:
import msal, openeo
TENANT, CLIENT_ID = "<tenant_id>", "<client_id>"
app = msal.PublicClientApplication(
CLIENT_ID, authority=f"https://login.microsoftonline.com/{TENANT}"
)
flow = app.initiate_device_flow(scopes=[f"api://{CLIENT_ID}/openeo"])
print(flow["message"]) # sign in at microsoft.com/devicelogin
token = app.acquire_token_by_device_flow(flow)["access_token"]
connection = openeo.connect("http://127.0.0.1:8083/").authenticate_oidc_access_token(token)
In the Entra app registration:
- Register a public client; both grants openEO clients use — authorization code with PKCE, and device code — are supported.
- Add your openEO client's redirect URL (the Web Editor's is its own origin).
- If your clients present access tokens audienced at your own API rather
than ID tokens, add that audience:
TITILER_OPENEO_AUTH_OIDC_AUDIENCES="api://<client_id>".
Entra's sub is pairwise: stable for a given user in a given app
registration, and different if you re-register the app. If you expect to
re-register, consider pinning identity to the tenant-stable object id with
TITILER_OPENEO_AUTH_OIDC_USER_ID_CLAIM="oid" — but decide before going live,
per the warning above.
For asset access on Microsoft Planetary Computer, see Microsoft Planetary Computer. Note that Entra identifies your users; it does not change what they can read there.
GET /credentials/oidc returns 404¶
That is expected when the backend is not configured for OIDC. openEO backends
advertise only the authentication method they actually support, so
/credentials/oidc and /credentials/basic are mutually exclusive — the
one that does not apply is never registered:
TITILER_OPENEO_AUTH_METHOD |
Registered route |
|---|---|
basic (default) |
/credentials/basic |
oidc |
/credentials/oidc |
Set TITILER_OPENEO_AUTH_METHOD=oidc together with CLIENT_ID and WK_URL.
If any of those are missing the backend now refuses to start and names the
variable, rather than starting and failing on the first request.
Token Validation¶
Validation is performed in the _verify_token
and _verify_claims methods:
- The header's
algmust beRS256, the only algorithm this backend verifies. - The signature is verified against the provider's JWKS. If the token's key id is not in the cached key set, the JWKS is refetched once — providers rotate signing keys continuously, and Entra publishes six at a time.
issmust match the discovery document'sissuer.audmust contain the client ID or one of the configuredAUDIENCES, orazpmust equal one of them.expis required, and checked with 60 seconds of clock-skew allowance.nbfis checked when present.
The discovery document is cached for an hour rather than for the life of the
process, so a provider that moves its jwks_uri does not break a running
deployment permanently.
User Information¶
Upon successful validation, a User object is created with:
user_id: the claim named byUSER_ID_CLAIM(defaults tosub)email: Email claim if availablename: Value from the configured name claim (defaults to "name")
Security Considerations¶
- Keep your client ID secure
- Configure appropriate token expiration times
- Use HTTPS in production
- Review and limit the requested scopes
- Regularly rotate any client secrets if used
For more details on the implementation, see the auth module source code.