Hi everyone,
at the moment, the OIDC authenticator issues tokens that simply identify the user with no restrictions where and how they can be used. This is a problem for the MCP server extension because the MCP specification mandates that the authentication token must be restricted to the MCP server resource.
This concerns not only the MCP extension because support for resources and scopes must be implemented in the OIDC extension and I think we should also reach a consensus how we want to apply resources and scopes to the existing XWiki REST API (the MCP server is currently implemented as a REST resource). In the end, as token authentication seems like a more and more requested feature, we could even think about moving it into XWiki itself.
Resources in OAuth 2.0 are defined as absolute URIs that may correspond to the actual location of the resource. The MCP specification strengthens that to say that the resource must correspond to the actual URL of the MCP server.
Scopes are less strictly defined, they are just space-delimited, case-sensitive strings and usually define the level of access on a resource, so the simplest scopes would be view and edit in the case of XWiki. In a more fine-grained setup, they could also, e.g., define what properties of a user profile can be accessed.
How we could implement support for this:
- We introduce a role that allows defining resources, each component implementation is a resource.
- A generic “REST” resource provides access to all REST resources that aren’t matched by a more specific scope.
- Tokens without resource defined stay valid for any request.
- When a token has resources defined, the authenticator validates the request URL against the defined resources, and denies access if it doesn’t match.
- For the generic “REST” resource, the scopes are matched against the request method: “view” allows only GET requests, other kinds of requests require the “edit” scope. The scope is recorded in an execution context property, so any code can check against them.
For the role, we could have the following signature:
@Role
public interface OIDCProtectedResource
{
URI getURI(); // canonical RFC 8707 audience, e.g. https://host/xwiki/rest/mcp
List<OIDCScope> getScopes(); // scopes this resource defines (may be empty)
default boolean isAudienceRequired() { return false; } // MCP → true
default boolean isAuthenticationRequired() { return false; }
default boolean matches(URI requestURI) { /* segment-safe prefix match */ }
default void checkScope(Scope tokenScope, HTTPRequestInfo req) throws OIDCScopeException { }
}
The idea of this is the following:
getURI: The URI is required by the standard as identifier.getScopes: The scopes define what scopes the user can select/confirm in the consent screen.isAudienceRequired: If a token without resource (named audience in this context, but this is the same) can be used. The MCP specification requires this, but by default we would allow tokens without resources to continue working for other resources.isAuthenticationRequired: the authenticator/a filter defined in the authenticator would automatically send a correct error leading the client to the respective login flow when this is set and the user isn’t logged in. It’s not clear to me if this would be absolutely required at this level or if we couldn’t simply map 401 errors accordingly.matches: Not in all cases it is desired that the URI is a prefix of the request URI. In some cases, for example, you might want to present a REST resource that has a URIrest/wikis/{wiki}/myResourceas a single resource across all wikis. This is the most flexible form, an alternative would be to have a method that returns a JAX-RS path specification with placeholders and the request is matched against that.checkScopechecks if the scope of the supplied token is valid for the given request.
Questions do decide:
- Should the checks for scope or authentication required be at the
OIDCProtectedResourcelevel or rather in the actual resource implementation, based on the OIDC context in the execution context? I tend towards saying that inOIDCProtectedResourceis the right place for checks that validate that the token is valid for this request, indicating to the client to get a new token when not (so get a token when none present, get a token for the right resource and scope when the resource/scope don’t match), while in the resource itself we would validate that the user has the correct access rights, which cannot be fixed by requesting a new token. - Is it really one component per resource, or should we maybe add a way to have one component that can define several resource URIs, e.g., one for every subwiki. I think for the MCP server, we would actually need one resource per subwiki as, in the current version, there is an MCP server per subwiki, with the idea that you could have MCP servers only exposing a single subwiki, or the main wiki one exposing access to the whole farm.
Some notes:
- At the moment, the idea is not that token scopes have any effect on authorization results. It is a block at resource level, if an accessible page exposes a tool to modify pages that can be executed with a GET request in the REST API (by including rendered output), at least in a first version, there is nothing planned to stop that. We could see if we could add deeper integration such that scopes actually influence rights, but that’s not what I’m proposing here.
- Likewise, an “edit” scope token does not grant any “edit” right, rights at page level are still checked. There is also no check planned if a user has edit right anywhere when granting an “edit” scope token.
- While the scope and resource would be defined for an OIDC token, we would not actually store them inside the token but rather on the server.
Thank you very much for your feedback!