Repository navigation
[Server] Narrow authorization to the resource server role - #532
Conversation
| private function escapeHeaderValue(string $value): string | ||
| { | ||
| return str_replace(['\\', '"'], ['\\\\', '\\"'], $value); | ||
| return str_replace(['\\', '"'], ['\\\\', '\\"'], preg_replace('/[\x00-\x1F\x7F]/', '', $value) ?? ''); |
There was a problem hiding this comment.
maybe worth a comment ? (for human readers :p)
| } | ||
| } | ||
|
|
||
| return array_values(array_unique($required)); |
There was a problem hiding this comment.
I like the readability but not a huge fan of the algorithm's complexity, imo a simple foreach loop with an indexed array would lower the complexity behind $required here. (nit)
soyuka
left a comment
There was a problem hiding this comment.
Nice refactoring/cleanup!
|
Thanks Chris. The narrowing makes sense to me. I checked Needs a fix before merge
Smaller points
Question on the token seam
Smoke test I'm happy to run a smoke test against the Sulu MCP bundle once this is ready. Ping me when the findings are in. |
930571a to
8be5fe1
Compare
* Remove OAuthProxyMiddleware, ClientRegistrationMiddleware and their interfaces (ADR 0002) * Replace OAuthRequestMetaMiddleware with RequestContext::getAccessToken() * Add ScopePolicy to AuthorizationMiddleware for 403 insufficient_scope step-up * JwtTokenValidator: final, CachedKeySet with refetch on unknown kid, alg allowlist, token type, leeway * ProtectedResourceMetadata: require resource, derive metadata path and challenge URL from it, enforce https * Expose WWW-Authenticate via CORS
Keys without alg are tagged with the token's algorithm instead of the first allowed one.
There was a problem hiding this comment.
🟡 Changes recommended
URL validation, metadata routing, scope validation, and global JWT leeway handling contain unresolved correctness and security issues.
5 open findings
What changed in this PR
Narrows OAuth support to a resource-server-only model and adds request-scoped token and scope enforcement.
Changes:
- Removes OAuth proxy, dynamic registration, and legacy discovery abstractions.
- Adds
AccessToken,ScopePolicy, hardened JWT validation, and derived resource metadata. - Updates OAuth examples, tests, documentation, CORS, and ADRs.
| File | Description |
|---|---|
tests/Unit/Server/Transport/StreamableHttpTransportTest.php |
Updates CORS header expectations. |
tests/Unit/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicyTest.php |
Removes obsolete policy tests. |
tests/Unit/Server/Transport/Http/OAuth/SecureUrlTest.php |
Tests secure URL validation. |
tests/Unit/Server/Transport/Http/OAuth/ScopePolicyTest.php |
Tests scope policies and hierarchies. |
tests/Unit/Server/Transport/Http/OAuth/ProtectedResourceMetadataTest.php |
Tests metadata validation and locations. |
tests/Unit/Server/Transport/Http/OAuth/ProtectedResourceMetadataHandlerTest.php |
Updates metadata handler tests. |
tests/Unit/Server/Transport/Http/OAuth/OidcDiscoveryTest.php |
Reworks JWKS discovery tests. |
tests/Unit/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicyTest.php |
Removes obsolete policy tests. |
tests/Unit/Server/Transport/Http/OAuth/JwksProviderTest.php |
Removes obsolete provider tests. |
tests/Unit/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddlewareTest.php |
Tests derived metadata routing. |
tests/Unit/Server/Transport/Http/Middleware/OAuthRequestMetaMiddlewareTest.php |
Removes legacy propagation tests. |
tests/Unit/Server/Transport/Http/Middleware/OAuthProxyMiddlewareTest.php |
Removes proxy tests. |
tests/Unit/Server/Transport/Http/Middleware/CorsMiddlewareTest.php |
Verifies exposed authentication header. |
tests/Unit/Server/Transport/Http/Middleware/ClientRegistrationMiddlewareTest.php |
Removes registration tests. |
tests/Unit/Server/Transport/Http/Middleware/AuthorizationMiddlewareTest.php |
Tests token and scope enforcement. |
tests/Unit/Server/Transport/AccessTokenFlowTest.php |
Tests end-to-end token propagation. |
tests/Unit/Server/Authorization/AccessTokenTest.php |
Tests the access-token value object. |
src/Server/Transport/TransportInterface.php |
Adds tokens to message callbacks. |
src/Server/Transport/StreamableHttpTransport.php |
Propagates validated tokens. |
src/Server/Transport/StatelessHttpTransport.php |
Propagates tokens statelessly. |
src/Server/Transport/ManagesTransportCallbacks.php |
Updates callback signatures. |
src/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicy.php |
Removes strict discovery policy. |
src/Server/Transport/Http/OAuth/SecureUrl.php |
Adds secure URL parsing. |
src/Server/Transport/Http/OAuth/Scopes.php |
Adds scope normalization. |
src/Server/Transport/Http/OAuth/ScopePolicy.php |
Adds request scope policies. |
src/Server/Transport/Http/OAuth/ProtectedResourceMetadata.php |
Derives and validates resource metadata. |
src/Server/Transport/Http/OAuth/OidcDiscoveryMetadataPolicyInterface.php |
Removes policy interface. |
src/Server/Transport/Http/OAuth/OidcDiscoveryInterface.php |
Removes discovery interface. |
src/Server/Transport/Http/OAuth/OidcDiscovery.php |
Narrows discovery to JWKS resolution. |
src/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicy.php |
Removes lenient policy. |
src/Server/Transport/Http/OAuth/JwtTokenValidator.php |
Hardens JWT validation and caching. |
src/Server/Transport/Http/OAuth/JwksProviderInterface.php |
Removes JWKS provider contract. |
src/Server/Transport/Http/OAuth/JwksProvider.php |
Removes legacy JWKS provider. |
src/Server/Transport/Http/OAuth/ClientRegistrarInterface.php |
Removes registration contract. |
src/Server/Transport/Http/OAuth/AuthorizationResult.php |
Returns validated access tokens. |
src/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddleware.php |
Routes derived metadata endpoints. |
src/Server/Transport/Http/Middleware/OAuthRequestMetaMiddleware.php |
Removes body metadata injection. |
src/Server/Transport/Http/Middleware/OAuthProxyMiddleware.php |
Removes authorization proxying. |
src/Server/Transport/Http/Middleware/CorsMiddleware.php |
Exposes WWW-Authenticate. |
src/Server/Transport/Http/Middleware/ClientRegistrationMiddleware.php |
Removes dynamic registration. |
src/Server/Transport/Http/Middleware/AuthorizationMiddleware.php |
Enforces bearer tokens and scopes. |
src/Server/Transport/BaseTransport.php |
Passes tokens through callbacks. |
src/Server/Stateless/StatelessProtocol.php |
Adds tokens to stateless contexts. |
src/Server/RequestContext.php |
Exposes validated access tokens. |
src/Server/Protocol.php |
Associates tokens with requests. |
src/Server/Authorization/AccessToken.php |
Adds the token value object. |
src/Exception/ClientRegistrationException.php |
Removes registration exception. |
phpunit.xml.dist |
Removes deleted example tests. |
examples/server/oauth-microsoft/tests/Unit/MicrosoftJwtTokenValidatorTest.php |
Removes custom validator tests. |
examples/server/oauth-microsoft/server.php |
Converts Entra example to a resource server. |
examples/server/oauth-microsoft/README.md |
Documents the revised Entra setup. |
examples/server/oauth-microsoft/MicrosoftJwtTokenValidator.php |
Removes insecure custom validator. |
examples/server/oauth-microsoft/McpElements.php |
Reads claims through AccessToken. |
examples/server/oauth-microsoft/env.example |
Removes obsolete secrets. |
examples/server/oauth-microsoft/docker-compose.yml |
Adds cache storage. |
examples/server/oauth-keycloak/server.php |
Adds resource-bound validation and scopes. |
examples/server/oauth-keycloak/README.md |
Documents revised Keycloak behavior. |
examples/server/oauth-keycloak/McpElements.php |
Reads claims through AccessToken. |
examples/server/oauth-keycloak/keycloak/mcp-realm.json |
Binds tokens to the resource URI. |
examples/server/oauth-keycloak/docker-compose.yml |
Adds cache storage. |
composer.json |
Suggests JWT and PSR-6 dependencies. |
CHANGELOG.md |
Records authorization BC breaks. |
adr/README.md |
Lists the new ADR. |
adr/0002-resource-server-only.md |
Establishes resource-server-only scope. |
adr/0001-oauth-authorization-server-out-of-scope.md |
Records amendment by ADR 0002. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
8be5fe1 to
0082fb1
Compare


Narrows the auth layer to the resource server role, see ADR 0002 - the proxy and client registration couldn't be hardened without becoming an authorization server, and the 2026-07-28 spec doesn't need them since the metadata points clients at the AS directly.
[BC Break] across
Mcp\Server\Transport\Http\OAuth& the auth middleware, see CHANGELOG under 0.9.0.OAuthProxyMiddleware,ClientRegistrationMiddleware,ClientRegistrarInterface- DCR is deprecated in 2026-07-28 and the proxied flow conflicts with RFC 9207issvalidation and CIMDOAuthRequestMetaMiddlewarewithRequestContext::getAccessToken()- handlers get the validated token from the request context instead of_meta.oauth, and the body isn't re-encoded anymoreScopePolicyonAuthorizationMiddlewarefor per-method/per-tool scopes incl. hierarchy =>403 insufficient_scopeJwtTokenValidatoris final, uses firebase'sCachedKeySet(refetch on unknownkid), enforces thealgallowlist, optionaltyp/leewayProtectedResourceMetadatarequiresresource, derives the metadata path & challenge URL from it, https except loopbackWWW-Authenticateexposed via CORSChecked Drupal's
mcp_server/mcp_server_oauth, Sulu'sSuluMcpBundle, API Platform, Shopware andsymfony/mcp-bundle- none of them uses the removed or changed classes.Not run against a live Keycloak or Entra yet - the examples need a manual check.
cc @Nyholm @CodeWithKyrian @soyuka WDYT?