Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ba1ash opened this issue Aug 22, 2024 · 9 comments

Comments

@ba1ash
Copy link

ba1ash commented Aug 22, 2024

​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:

  • access token has been expired. In this case we need a way to get a fresh access token.

  • refresh token has been expired. Probably, it means user has to renew its session.

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
Copy link
Member

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
Copy link

SagarGi commented Sep 12, 2024

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 the backend 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
Copy link

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
Copy link

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
Copy link
Member

julien-nc commented Sep 19, 2024

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:

  • user_oidc emits the OCA\UserOIDC\Event\TokenObtainedEvent event when the ID token + access token + refresh token have been obtained (end of the login flow)
  • integration_openproject listens to this event and, when it gets it, emits a OCA\UserOIDC\Event\TokenExchangeRequest event (with some parameters about the scopes)
  • user_oidc receives this event and
    • does the token exchange
    • stores the tokens in the Php session (just like integration_swp does)
    • emits a OCA\UserOIDC\Event\TokenExchangeDone event which contains an ID for this token
  • integration_openproject receives this event and will then use this ID each time it needs to get the token (by emitting a OCA\UserOIDC\Event\GetExchangedToken event)
  • user_oidc takes care of refreshing all the stored exchanged tokens. The token expiration checks can be lazy or very frequent:
    • lazy: Check and refresh (if needed) a token when an app (like integration_openproject) wants to get a token via OCA\UserOIDC\Event\GetExchangedToken.
    • frequent: More like integration_swp, in user_oidc/lib/AppInfo/Application.php, check very often if some of the stored tokens need to be refreshed and refresh them immediately

What do you think?

@SagarGi
Copy link

SagarGi commented Sep 23, 2024

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
Copy link
Member

@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
Copy link

SagarGi commented Sep 24, 2024

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

@edward-ly edward-ly added enhancement New feature or request priority: normal and removed feature request labels Oct 2, 2024
@individual-it
Copy link

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants