PrefectHQ/fastmcp · error · ValueError

AzureProvider requires at least one non-OIDC scope in requir

Error message

AzureProvider requires at least one non-OIDC scope in required_scopes (e.g., 'read', 'write'). OIDC scopes like 'openid', 'profile', 'email', and 'offline_access' are not included in Azure access token claims and cannot be used for scope enforcement.

What it means

AzureProvider enforces scopes against claims embedded in the Azure access token. OIDC scopes (openid, profile, email, offline_access) are never included in those token claims, so if required_scopes contains only OIDC scopes there is nothing enforceable — every request would fail scope checks. The constructor therefore rejects such configurations with ValueError.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/providers/azure.py:236

        self._obo_supported = True

        # Apply defaults
        self.identifier_uri = identifier_uri or f"api://{client_id}"
        self.additional_authorize_scopes: list[str] = parsed_additional_scopes

        # Always validate tokens against the app's API client ID using JWT
        issuer = token_issuer or f"https://{base_authority}/{tenant_id}/v2.0"
        jwks_uri = f"https://{base_authority}/{tenant_id}/discovery/v2.0/keys"

        # Azure access tokens only include custom API scopes in the `scp` claim,
        # NOT standard OIDC scopes (openid, profile, email, offline_access).
        # Filter out OIDC scopes from validation - they'll still be sent to Azure
        # during authorization (handled by _prefix_scopes_for_azure).
        validation_scopes = [
            s for s in (parsed_required_scopes or []) if s not in OIDC_SCOPES
        ]
        if not validation_scopes:
            raise ValueError(
                "AzureProvider requires at least one non-OIDC scope in "
                "required_scopes (e.g., 'read', 'write'). OIDC scopes like "
                "'openid', 'profile', 'email', and 'offline_access' are not "
                "included in Azure access token claims and cannot be used for "
                "scope enforcement."
            )

        token_verifier = JWTVerifier(
            jwks_uri=jwks_uri,
            issuer=issuer,
            audience=[client_id, self.identifier_uri],
            algorithm="RS256",
            required_scopes=validation_scopes,  # Only validate non-OIDC scopes
            http_client=http_client,
        )

        # Build Azure OAuth endpoints with tenant
        authorization_endpoint = (

View on GitHub (pinned to 1f02114297)

Solutions

  1. Add at least one application-defined scope (e.g. 'read', 'write', or an api://... scope exposed by your app) to required_scopes.
  2. If you only want OIDC scopes sent during login, remove required_scopes entirely — OIDC scopes are still sent to Azure via _prefix_scopes_for_azure without being enforced.
  3. Enforce OIDC scope presence with a custom token verifier if truly needed, not via required_scopes.

Example fix

// before
AzureProvider(client_id=cid, client_secret=sec, required_scopes=["openid", "profile"])
// after
AzureProvider(client_id=cid, client_secret=sec, required_scopes=["read", "write"])
Defensive patterns

Strategy: validation

Validate before calling

OIDC_SCOPES = {"openid", "profile", "email", "offline_access"}
if required_scopes and not (set(required_scopes) - OIDC_SCOPES):
    raise ValueError("AzureProvider required_scopes needs at least one non-OIDC scope")

Type guard

def has_enforceable_scopes(scopes: list[str] | None) -> bool:
    oidc = {"openid", "profile", "email", "offline_access"}
    return bool(scopes) and bool(set(scopes) - oidc)

Try / catch

try:
    provider = AzureProvider(client_id=cid, client_secret=sec, required_scopes=scopes)
except ValueError as e:
    if "non-OIDC scope" in str(e):
        provider = AzureProvider(client_id=cid, client_secret=sec, required_scopes=[*scopes, "read"])
    else:
        raise

Prevention

When it happens

Trigger: AzureProvider(client_id=..., client_secret=..., required_scopes=["openid", "profile"]) — i.e. parsed required_scopes minus OIDC_SCOPES yields an empty list.

Common situations: Reusing a required_scopes list meant for the authorization request (openid/profile) as the enforcement list; copying Google/GenericOAuth examples where OIDC scopes are legitimate requirements; assuming Azure validates OIDC scopes in tokens.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/776479cb53288242. Report an issue: GitHub.