nextcloud / user_oidc

OIDC connect user backend for Nextcloud
GNU Affero General Public License v3.0
89 stars 36 forks source link

[Feature Request] Provide OIDC generated access token to other apps. Support OIDC token exchange. #925

Open ba1ash opened 3 months ago

ba1ash commented 3 months ago

​Ever more often Nextcloud is bundled with other applications, such as OpenProject, sharing one user session across the integrated applications via OIDC single sign on (SSO).

For deep integrations Nextcloud apps need to be able to make impersonated back-end to back-end API requests to bundled applications.

Up until now the impersonation was often achieved via OAuth2 flows, which has an inferior UX as it is pretty complex for user to understand. It requires involvement of the user. And the cognitive load often is too high. They fail. A better approach would be to use the trusted OIDC provider to hand out access tokens during SSO that then can be used to authorize requests to bundled applications. Then the user only needs to login and no further actions by the user are necessary.

We believe that user_oidc would be the ideal place for managing the access tokens. It has all the setup/configuration information needed to interact with the OIDC provider. Further it already receives the ID, access and refresh tokens.

As developers of OpenProject Integration app we would like to have access to the access token with sufficient privileges to use it for authorizing outgoing requests to the OpenProject server. By sufficient privileges we mean, for instance, presence of OpenProject server client_id in aud claim of the access token and required scopes in scope claim. We see two possible scenarios how to get such a token:

  1. OIDC provider is configured to add OpenProject client_id to aud claim for access token issued to Nextcloud client_id.

  2. OIDC provider is configured to support token_exchange. Then the original access token can be exchanged for another one exclusively used to access OpenProject (having only the OpenProject client_id in the aud claim).

Then the access to this access token could be given to other Nextcloud applications, integration_openproject among them, as an event subscription or in any different way.

Besides that the following cases should be taken into consideration:

We are not sure about the exact API that could be provided to cover these scenarios. 

Kindly let us know if what we ask is possible to achieve.

@julien-nc @wielinde @SagarGi

julien-nc commented 2 months ago

Hi, To make user_oidc able to take care of getting new access tokens (with token exchange) to access specific remote services, it should have a new "subscription" mechanism for other apps to ask for it.

It seems easier to perform this token exchange in integration_openproject when getting the login token (when handling the TokenObtainedEvent event).

Maybe I'm missing your point. Are you saying it's harder to do this in the app receiving the login token rather than directly in user_oidc?

SagarGi commented 2 months ago

Hi @julien-nc As per my understanding, Yes we can implement the whole case in our app (token handling) but the thing that the above issue point out is the same token handling thing can also might be required for other application (may be later) and those token handling thing would be repeated i guess. In addition to that, also once we get the token we have to refresh it again and again for thebackend to backend communication. That thing would also be great if it can be possible to be implemented and handled by user_oidc. In general it would be a feature request in the user_oidc app.

CC @ba1ash @wielinde @individual-it can also add some thoughts regarding it.

wielinde commented 2 months ago

Exactly what @SagarGi wrote. The idea is to centralize functionality in one space that many apps would need in common. Retrieving of tokens, storing of tokens, updating of tokens and exchanging of tokens. Even though each app could build it themselves I believe that we all would be better off centralizing the code.

individual-it commented 2 months ago

The piece that would be hard to build into each app is the renewal of the refresh tokens as we cannot be sure that the code of the app is somewhere executed before the refresh token has to be renewed. Better to have that functionality embedded in the OIDC app

julien-nc commented 2 months ago

Hey there. We don't have the resources in the following months to dive into this. Here are my suggestions:

  1. You implement something similar than what's done in integration_swp (store the token and refresh it when needed) + the token exchange
  2. You make a PR in user_oidc exposing a generic token exchange mechanism (we can help with specifying this and reviewing it)

If you prefer going with 2., here is a rough plan that came to mind:

What do you think?

SagarGi commented 2 months ago

Suggestion 2 sounds good to me. @julien-nc from your suggestion, i might lack knowledge on what you have mentioned but can't we simply do something like this?

  1. user_oidc saves ID token + access token + refresh token (which i think it already does). And since this token is of no use for nextcloud (as far as i know since it creates a user session on its own once it has been authenticated) and also for integration_openproject (the targeted aud is not set yet) for backend to backend request.
  2. In user_oidc built a mechanism to keep access_token alive since it is used for token exchange for a new token.
  3. Implement in user_oidc the token exchange mechanism when Nextcloud wants a token to have a backend to backend request to OpenProject through integration_openproject with a TokenExchangeRequest event whose information can be save it in our integration app itself (may be).
  4. We use the token that we got from token exchange (with other client aud for backend to backend request) and when the token cannot be used anymore we again ask user_oidc a new token (since the user_oidc keeps alive the old token so that new token can be obtained through token exchange).

I do not know if it is the same thing that @julien-nc has mentioned in suggestion 2.

@individual-it @wielinde @ba1ash

julien-nc commented 2 months ago

@SagarGi I updated my previous comment with more details.

Reaction to your last comment:

  1. user_oidc does not save the id/access/refresh tokens, it just emits the OCA\UserOIDC\Event\TokenObtainedEvent which contains them
  2. The goal is to NOT us the access token received during the login but rather that integration_openproject tells user_oidc to contact the IdP to exchange the token for another one that will be used by integration_openproject to authenticate against OpenProject.
  3. If you want to really factorize the logic in user_oidc, then integration_openproject should not be in charge of storing or refreshing the exchanged tokens. integration_openproject does not need to be aware that a toke needs to be refreshed, the refresh can be handled by user_oidc (see my previous comment about "lazy" and "frequent" refresh strategies). integration_openproject could just store and identificator for the exchanged token that user_oidc has gotten and stored, so when the token is needed, it can be obtained by sending a "get" event using this identificator as event parameter.

I hope that makes it clearer. It's just my recommendation on how to implement this. Feel free to suggest other approaches.

SagarGi commented 2 months ago

@julien-nc Thanks for the reaction. The updated comment of yours is more clearer. And it seems to be doable.

individual-it commented 1 month ago

@julien-nc here is the proposed flow chart: token_handling.pdf As just discussed, we can also use the event system to call into the user_oidc app