Skip to content

Conversation

@ruediste
Copy link

Add the ability to authenticate using an Authorization: Bearer <access token> HTTP header.

@tmortagne
Copy link
Member

tmortagne commented Jun 13, 2025

Thanks for the contribution @ruediste!

Would be better to:

(see https://dev.xwiki.org/xwiki/bin/view/Community/DevelopmentPractices fore more detail about all dev practices)

Comment on lines 178 to 183
jwtClaimsSet = jwtProcessor.process(authorizationHeaderValue.substring("Bearer ".length()), null);

ClaimsSet claimsSet = new ClaimsSet();
claimsSet.putAll(jwtClaimsSet.toJSONObject());

UserInfo userInfo = new UserInfo(jwtClaimsSet);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand your process here. You don't seem to ever validate the token on the provider and seems to expect it to be a complete (signed) user info, which is not really what an access token is supposed to be in OIDC AFAIK.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understood the JWT Processor performs the validation against the JWKS set specified in the constructor.

The issue access token vs id token vs user info bothered me during implementation as well. I'd like not to request the full user info from the provider for each request. However, some caching could help here.

The UserInfo is required for the group checking. This could be dropped altogether, as an existing user should also have the required groups. Unless the groups are beeing changed after the user has been created.

The UserInfo is also required to determine he XWiki User ID from the token (via the substitutor). This part can obviously not be skipped.

What do you think, what is the best option?

  1. Keep as-is
  2. Drop the Group Check
  3. Use a cached User Info

Copy link
Member

@tmortagne tmortagne Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My worry is that your solution seems a bit limited and not very standard. Another problem is that since it's not really a token, and it's never validated, it's not possible to drop it, so if someone get a hand on this signed user info it will keep working more or less forever (unless you reset the provider's keys).

It feels much more standard to accept real tokens and validate them on the provider. On the performance side of things, I agree with you that re-doing the token validation on the provider and user authorization/membership sync with every HTTP request does not sound like the best idea. But having some caching, and then some background thread in charge of regularly re-validating the cached tokens and re-sync info (in case it's been invalidated, or some authorization related info changed on provider side) should do the job.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the code again. To get full compatibility with the existing login logic, I propose the following approach:

  • Send both an id and access token using X-Id-Token and X-Access-Token headers
  • Validate the signature and audience of the Id token (like I did in this PR for the access token)
  • Use the access token to retrieve the user info
    What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the mechanism above. Please review.

@ruediste ruediste changed the title Support Access Tokens OIDC-243: API Access using Accesstoken Jun 27, 2025
if (configuration.isAllowAccessToken()) {
HttpServletRequest request = context.getRequest().getHttpServletRequest();
String idTokenHeader = request.getHeader("X-Id-Token");
String accessTokenHeader = request.getHeader("X-Access-Token");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to receive the access token as a Bearer Authorization header ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could certainly be done this way. But for the Authorization header I would expect that single header to be sufficient for authorization, thou we would have to combine the two tokens. And sending the id token is non-standard anyways. I would prefer to leave it as-is.

String idTokenHeader = request.getHeader("X-Id-Token");
String accessTokenHeader = request.getHeader("X-Access-Token");

if (idTokenHeader != null && accessTokenHeader != null) {
Copy link
Member

@tmortagne tmortagne Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me the ID token should be optional (I'm not even sure it's needed at all actually as there is probably a way to request an ID token from the provider, with the access token).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this and have not found a way to get one.

{

@Override
public DocumentReference load(Pair<String, String> key) throws Exception
Copy link
Member

@tmortagne tmortagne Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unfortunately have no experience with this use case (and not much time to research it), which makes it hard to help you. That being said, my understanding is that this is supposed to be covered with the capacity of the /token endpoint to take in input a JWT Bearer token instead of the authorization code (that you get in a code flow). That way you end up with the ID token and a new access token that you can then use to request the UserInfo exactly like in the usual authentication (i.e. you can just reuse the same code that already exist after the response of the token endpoint).

See the following references:

Of course, if it becomes too time-consuming for you to try to find the right standard in that OIDC/OAuth2 jungle, an easier alternative for now would be to create your own custom authenticator extending OIDCAuthServiceImpl and adding that layer of token support. That way, you can take shortcuts that feat exactly your need and specific setup without the constraint of being generic enough for the standard OIDC Authenticator.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have studied the documentation and found the following: In the JWT Bearer Flow you obtain a special JWT that identifies you as a user. This special JWT is then sent to the idP to get an access token and id token.

For that to work the idP has to be configured accordingly, which is not done by default. Obviously, it has to be configured which JWTs the idP accepts. I tested with a keycloak, using an access token generated with the same keycloak instance, and it did not work out of the box.

Therefore I think this route is a dead end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore I think this route is a dead end.

Well I would not call that a dead end in general since it does sounds like a good standard for that use case. But indeed if it's not properly supported by the provider you are relying on, it's not going to help you.

@ruediste ruediste force-pushed the access-token branch 3 times, most recently from 3c3e052 to 903114c Compare July 11, 2025 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants