feat: replace RemoteAuthProvider with OIDCProxy and add pluggable backend token strategies#23
feat: replace RemoteAuthProvider with OIDCProxy and add pluggable backend token strategies#23mdanish98 wants to merge 19 commits into
Conversation
…ssuer URL, strip client_id
…d client_id check
- Refactor TokenExchangeStrategy to reuse a single httpx.AsyncClient instance for all requests, reducing connection overhead. - Add error handling for missing access_token in the response and non-JSON responses. - Update documentation to clarify OAuth scopes and their enforcement. - Improve test coverage for token exchange scenarios and logging behavior.
ricogu
left a comment
There was a problem hiding this comment.
Reposting the P1/P2 findings inline for easier follow-up.
| forward_resource=False, # Disable RFC 8707 resource param — rejected by SAP IAS | ||
| # and unnecessary for non-resource-indicator IdPs. | ||
| require_authorization_consent="external", # Consent handled by upstream IdP | ||
| client_storage=MemoryStore(), # Avoid disk storage — container home dir may not exist |
There was a problem hiding this comment.
This makes OAuth state process-local. FastMCP stores transactions, DCR clients, JTI mappings, refresh metadata, and upstream token references in this store, so auth can break after restarts or across multiple replicas/load-balanced pods. Please avoid hard-coding MemoryStore; use FastMCP's default persistent store or make storage configurable and document the production requirement for shared storage or sticky sessions.
| "Either set BACKEND_AUTH_TOKEN_ENDPOINT explicitly, or set OAUTH_ISSUER_URL " | ||
| "so the token endpoint can be derived automatically." | ||
| ) | ||
| token_endpoint = _derive_token_endpoint_from_issuer(issuer_url) |
There was a problem hiding this comment.
Deriving <issuer>/oauth2/token is not a general OIDC rule and is wrong for common providers like Keycloak and Azure AD. Since BACKEND_AUTH_AUDIENCE auto-selects token exchange, this can create a broken default config. Please discover token_endpoint from OIDC metadata, or require BACKEND_AUTH_TOKEN_ENDPOINT for token exchange.
| # If you were using the previous RemoteAuthProvider-based OAuth configuration, | ||
| # the following changes are required: | ||
| # | ||
| # REMOVED: OAUTH_JWKS_URI (auto-discovered from OIDC config — remove this var) |
There was a problem hiding this comment.
This migration note is good, but the public docs still need the same update. README.md and client_config_examples.txt still mention OAUTH_JWKS_URI, JWKS path troubleshooting, and the old auth flow, so users following them will end up with stale config. Please update those docs in this PR too.
| extra_authorize_params["scope"] = " ".join( | ||
| dict.fromkeys(["openid", *required_scopes]) | ||
| ) | ||
| # If required_scopes is not set, don't send scope parameter at all. |
There was a problem hiding this comment.
The comment above says openid is requested by default, but this branch sends no scope parameter unless OAUTH_REQUIRED_SCOPES is set. Some OIDC providers treat a request without openid as plain OAuth and may not return the expected OIDC identity data. Please request at least openid by default, or make omitting scopes an explicit opt-out.
Summary
Replaces the previous
RemoteAuthProvider-based OAuth implementation with FastMCP'sOIDCProxy, enabling universal OIDC support for any standards-compliant IdP (SAP IAS, Azure AD, Keycloak, Okta) without requiring Dynamic Client Registration (DCR) support on the IdP side. Additionally introduces a pluggable backend token strategy system (BackendTokenProvider) supporting three modes: forward, RFC 8693 token exchange, and none.Changes
Added
src/aas_mcp_server/backend_auth.py— pluggable backend token strategy system withForwardStrategy,TokenExchangeStrategy(RFC 8693), andNoneStrategy;TokenExchangeStrategyholds a sharedhttpx.AsyncClientwithaclose()and async context manager supporttests/test_backend_auth.py— full unit test coverage for all three strategies, factory logic, response validation, client reuse, and graceful shutdowndocs/2026-06-06-kyma-backend-auth-guide-design.md— Kyma backend auth design guideModified
src/aas_mcp_server/server.pybuild_auth_provider: migrated fromRemoteAuthProvidertoOIDCProxy; hardens wildcard bind guard, normalises issuer URL, validatesclient_id; emits aWARNINGwhenOAUTH_REQUIRED_SCOPESis set explaining scope enforcement is per-authorization-flow only, not per-request (SEC-262)build_mcp_server: wiresbackend_token_providerinto HTTP client; wires_provider_lifespanintoFastMCP.from_openapi(lifespan=...)soTokenExchangeStrategy.aclose()is called on server shutdown (SEC-309)_provider_lifespan: new@asynccontextmanagerthat callsaclose()on any provider that supports itsrc/aas_mcp_server/http_client.pyBearerTokenAuth: delegates token acquisition toBackendTokenProvider; removes token prefix from debug logs — only strategy name and token length are loggedget_access_tokenimportsrc/aas_mcp_server/constants.py— addsBACKEND_AUTH_*env var constants, strategy name constants, and RFC 8693 grant type constantsconfig.yaml.template— full migration guide fromRemoteAuthProvidertoOIDCProxy; newSCOPE ENFORCEMENT CHANGEsection;OAUTH_REQUIRED_SCOPESexample corrected to comma-separated formatconfigs/config.yaml.template— corrects misleading# REQUIREDlabels on bothofficial_specandimplementation_spec; adds configuration guide for all three valid spec combinationstests/test_auth.py,tests/test_http_client.py,tests/test_server.py— updated forOIDCProxymigration; new tests for token log safety, lifespan shutdown, scope delegation warning; test isolation hardened (clear=Trueon env patches).gitignore— removes broaddocs/andcharts/ignore rulesRemoved
RemoteAuthProviderusage and associated JWT verifier configurationTechnical Details
OIDCProxy architecture
OIDCProxyacts as a local OAuth 2.1 Authorization Server, proxying the PKCE flow to the upstream IdP using pre-registered server-side credentials. Works with any OIDC-compliant IdP without requiring RFC 7591 DCR support.Scope enforcement change (operators upgrading from JWTVerifier)
Previously
OAUTH_REQUIRED_SCOPESwas enforced per-request viaJWTVerifier. WithOIDCProxy, enforcement is per-authorization-flow: scopes are requested during PKCE and the IdP refuses authorization to users who do not hold them. FastMCP-issued tokens are not re-validated per-request because MCP clients register via DCR without declaring scopes. AWARNINGis emitted at startup whenOAUTH_REQUIRED_SCOPESis set.Backend token strategies
forward(default)token_exchangeaud; preserves usersubnoneAuto-selection: if
BACKEND_AUTH_AUDIENCEis set,token_exchangeis selected automatically.Connection lifecycle (SEC-309)
TokenExchangeStrategyholds a singlehttpx.AsyncClientcreated at construction (not per-request).aclose()is wired into the FastMCP server lifespan via_provider_lifespan.Compatibility & Impact
Breaking Changes
OAUTH_REQUIRED_SCOPES: enforcement changes from per-request to per-authorization-flow. See migration guide inconfig.yaml.template.OAUTH_JWKS_URIno longer read — auto-discovered from OIDC config. Remove from existing deployments.OAUTH_CLIENT_IDnow required whenOAUTH_ISSUER_URLis set.OAUTH_CLIENT_SECRETrequired for confidential clients.API Changes
New OAuth endpoints exposed by
OIDCProxy:/.well-known/oauth-authorization-server(RFC 8414)/authorize,/token,/register,/auth/callbackTesting
tests/test_backend_auth.py— 20 tests covering all strategy paths, error conditions, client reuse, graceful shutdown_lifespan_manager()clear=Trueon env patches)Checklist