The problem
Microsoft Entra ID cannot advertise an application's custom API scopes in its OIDC discovery document. Its scopes_supported is tenant-wide and permanently fixed at ["openid", "profile", "email", "offline_access"]. This is by design, per Microsoft in AzureAD/microsoft-identity-web#1689:
The OIDC metadata endpoint just returns Azure AD's STS metadata. There's no concept of custom resources or custom scopes here.
OidcProviderInfo.__init__ intersects the scopes a backend declares at /credentials/oidc against that list (openeo/rest/auth/oidc.py, 0.48.0):
self._scopes = {"openid"}.union(scopes or []).intersection(self._supported_scopes)
A sensible default against a well-behaved provider but with Entra it drops the one scope that matters:
provider = OidcProviderInfo(
issuer=f"https://login.microsoftonline.com/{TENANT}/v2.0",
scopes=["openid", "profile", "email", f"api://{CLIENT_ID}/openeo"],
)
provider.get_scopes_string()
# -> 'email openid profile' the api:// scope never reaches Entra
With no resource scope requested, Entra issues an access token unusable and the backend (here titiler-openeo) rejects the token, and the user sees a signature error with no obvious connection to a dropped scope.
The net effect is that no openEO backend protected by Entra can be authenticated against with this client, and nothing the backend or the app registration can configure will change that.
Possible accommodations
Any of these would be enough:
- Warn instead of dropping keep the declared scopes,
log.warning when one is not advertised. Preserves the diagnostic value without the breakage.
- Opt out e.g.
OidcProviderInfo(..., strict_scopes=False), plumbed through Connection.authenticate_oidc*.
- Do not intersect at all RFC 8414 makes
scopes_supported RECOMMENDED and descriptive, and RFC 6749 §3.3 puts the grant decision on the authorization server, which reports the result in the scope response parameter.
Options 1 and 3 also help any other provider with incomplete metadata; option 2 leaves each user to discover the problem first.
Workaround
from openeo.rest.auth.oidc import OidcProviderInfo
_orig_init = OidcProviderInfo.__init__
def _patched_init(self, issuer=None, discovery_url=None, scopes=None, **kwargs):
_orig_init(self, issuer=issuer, discovery_url=discovery_url, scopes=scopes, **kwargs)
self._scopes = {"openid"} | set(scopes or [])
OidcProviderInfo.__init__ = _patched_init
Environment
openeo 0.48.0 · Python 3.13 · Entra ID v2.0 endpoint, single tenant · backend: titiler-openeo
The problem
Microsoft Entra ID cannot advertise an application's custom API scopes in its OIDC discovery document. Its
scopes_supportedis tenant-wide and permanently fixed at["openid", "profile", "email", "offline_access"]. This is by design, per Microsoft in AzureAD/microsoft-identity-web#1689:OidcProviderInfo.__init__intersects the scopes a backend declares at/credentials/oidcagainst that list (openeo/rest/auth/oidc.py, 0.48.0):A sensible default against a well-behaved provider but with Entra it drops the one scope that matters:
With no resource scope requested, Entra issues an access token unusable and the backend (here titiler-openeo) rejects the token, and the user sees a signature error with no obvious connection to a dropped scope.
The net effect is that no openEO backend protected by Entra can be authenticated against with this client, and nothing the backend or the app registration can configure will change that.
Possible accommodations
Any of these would be enough:
log.warningwhen one is not advertised. Preserves the diagnostic value without the breakage.OidcProviderInfo(..., strict_scopes=False), plumbed throughConnection.authenticate_oidc*.scopes_supportedRECOMMENDED and descriptive, and RFC 6749 §3.3 puts the grant decision on the authorization server, which reports the result in thescoperesponse parameter.Options 1 and 3 also help any other provider with incomplete metadata; option 2 leaves each user to discover the problem first.
Workaround
Environment
openeo0.48.0 · Python 3.13 · Entra ID v2.0 endpoint, single tenant · backend: titiler-openeo