Introducing support for resources and scopes in OIDC tokens

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 URI rest/wikis/{wiki}/myResource as 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.
  • checkScope checks 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 OIDCProtectedResource level or rather in the actual resource implementation, based on the OIDC context in the execution context? I tend towards saying that in OIDCProtectedResource is 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!

I think this could be a good addition to XS. Nowadays, the ability to issue a PAT (personal access token) seems like something software should have by default. So either provide it with XS by default or introduce a contrib extension for issuing PAT/OAuth2 tokens (and then have OIDC build on that extension). I don’t think users should have to install OIDC just to have the PAT feature.

Not answering to the rest ATM since I’d need to think more about it. Also, I think we need to define first where to put the resource permissions/scopes (OIDC, XS, in a OAuth extension).

Maybe I’m going too far and I’m starting to discuss what you said was out of scope for now in:

Thx

This is the current situation. That extension is part of the OAuth2/OIDC project, since it’s using the same tech than the OIDC Provider. But you don’t need to install the OIDC Authenticator or Provider extensions to have it, if that’s what you meant by “install OIDC”.

I meant not having to search for OIDC anywhere (nor seeing it) to install that extension, i.e. an extension with a name that means that it is. Also, I don’t think it’s right to have it inside the OIDC extension since it’s not related to OIDC, it’s more generic.

And as you can see, the extension name is “Token Authenticator” (since the goal is to authenticate using a token).

Again, it’s part of a generic project already. That project provides various extensions to manipulate various OAuth2 and OIDC (which is just an OAuth2 extension) related concepts.

Maybe I missed something. I thought it was in GitHub - xwiki-contrib/oidc: Make XWiki an identity provider that can be reused by any application · GitHub . If it’s not please tell me where it is. And if it’s in GitHub - xwiki-contrib/oidc: Make XWiki an identity provider that can be reused by any application · GitHub the you cannot say that “it’s part of a generic project already” because it isn’t (OIDC is not generic).

The README even says “Various tools to manipulate OpenID Connect protocol in XWiki.” but even if that was fixed, it would still not be generic. If you want it to be generic and not related to ODIC then the project’s name, java package names need to be modified.

As I mentioned, this project is about OAuth2 in general, and not just OIDC. It was initially named OIDC because if was the main target (i.e. you can see it as how much of the OAuth2 ecosystem we wanted to support), but it always supported also more limited OAuth2 use cases (as it’s often the case with “OIDC” tools). Sure, the README need to be updated a bit, but it’s obviously hardly a blocker.

What’s important is that, in practice, this project is documented as and is exposing all sort of OAuth2 tools (which include OAuth2 extensions like OIDC). And yes, from user point of view we do have a generic extension about generating and using bearer tokens to authenticate, and @MichaelHamann’s proposal is mainly about adding some scope support that.

Creating some new OAuth2 project would not make any kind of sense.