fusionauth-issues icon indicating copy to clipboard operation
fusionauth-issues copied to clipboard

Self-service account session revocation

Open jobannon opened this issue 2 years ago • 14 comments

Self-service account session revocation

Problem

In FusionAuth version 1.45.0 the self-service themed pages no longer use the SSO session, and instead have a discrete session.

Logging out of SSO, or your own application will not necessarily log you out of the session held for the /account pages which is essentially an extension of your own application.

For example, if you are logged into application A with a client_id of 377e77a7-e066-4896-b3b4-5c2bbaec96e0 and then navigate to /account/?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0 you will now have a new access token and refresh token for this application. This session is held in http only secure cookies.

If you then log out of SSO or your own application, the state for the /account pages is still valid.

In order to logout of the /account session, you need to make a GET request to /account/logout?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0.

Solution

One option would be to use single logout and then when logging out of an app w/ self-service enabled, we add another URL to the list of other application logout URLs for /account/logout?client_Id=377e77a7-e066-4896-b3b4-5c2bbaec96e0.

Another option is to listen for refresh token revocation events and if a token for application with Id 377e77a7-e066-4896-b3b4-5c2bbaec96e0 (in this example) is revoked, revoke the refresh token held for /account as well.

Needs more investigation.

Observed in version

  • 1.45.3

Affects versions

  • >= 1.45.0

Workarounds

  • Ensure that you remove the account session as part of your logout process.
    • GET /account/logout?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0
  • ~~Only side load the account with an existing access token using the Authorization header.~~
    • ~~This has limited use because you will need to be able to set the request header. This is generally done from a mobile app opening a web view.~~
  • Create a surrogate application for logout of the account application.
    • Assuming you have an application with id 377e77a7-e066-4896-b3b4-5c2bbaec96e0 and you are using the /account/?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0 route for self-service create a NEW application in FusionAuth under the same tenant
    • In the new application add a "Logout URL" of https://<YOUR_FUSIONAUTH_HOST>/account/logout/377e77a7-e066-4896-b3b4-5c2bbaec96e0 (ensure that you follow this format and not - https://<YOUR_FUSIONAUTH_HOST>/account/logout?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0 for this solution only)
    • Ensure the application 377e77a7-e066-4896-b3b4-5c2bbaec96e0 has "Logout behavior" set to All applications
    • When you log out of the application FusionAuth should call logout on the self-service app when performing the logging out operation
    • Verify by checking the account session is revoked when calling *auth2/logout.

Related

  • https://github.com/FusionAuth/fusionauth-issues/issues/1860
  • https://github.com/FusionAuth/fusionauth-issues/issues/2425

Additional context

Internal:

  • https://inversoft.slack.com/archives/C051S8N8E/p1685654998072059

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

jobannon avatar Jun 01 '23 21:06 jobannon

Using the account jwt side-load isn't a proper workaround for this. It is going to cause confusion over what it is in the fusionauth.sso cookie which can lead the browser getting into a bad state.

lyleschemmerling avatar Aug 17 '23 20:08 lyleschemmerling

Added surrogate application workaround steps

lyleschemmerling avatar Aug 17 '23 22:08 lyleschemmerling

It should be noted that the current behaviour can pose a security issue, since the self-service account feature can potentially provide a way to set a new user password without further authentication (i.e. subject to whether or not the 'Require current password' option added via https://github.com/FusionAuth/fusionauth-issues/issues/1578 is enabled or not).

There is also potential to change email address (e.g. which could enable authentication via magic link) without further authentication (as far as I can tell, the only "defence" against this aspect is to enable the Email update email template which sends a notification old + new email on change, but it doesn't prevent the attack: just notifies someone that it happened).

If a public/shared computer is used, an attacker can gain access to a previously "logged out" session (i.e. applications/SSO was logged out, but not the self-service account management session), and then take over the affected account!

dransome avatar Sep 22 '23 11:09 dransome

Added surrogate application workaround steps

@lyleschemmerling please can you clarify

Attempting to follow your surrogate application workaround steps verbatim:

Real application: 5f921ed5-1700-4f25-8ec4-7db9b267c96a Surrogate logout application: 59e7200e-47c4-49ff-8f53-d4b461dbfbe6

My test user is registered to real application, and has an existing self-service account session. I set the logout URL of surrogate logout application to https://<MY_FUSIONAUTH_HOST>/account/logout/5f921ed5-1700-4f25-8ec4-7db9b267c96a (replacing MY_FUSIONAUTH_HOST - obviously).

Then I point my (test user) browser at the logout URL of the surrogate logout application: https://<MY_FUSIONAUTH_HOST/oauth2/logout?client_id=59e7200e-47c4-49ff-8f53-d4b461dbfbe6

I got a HTTP 500, with this in the logs:

2023-09-22 12:02:13.693 PM ERROR io.fusionauth.app.primeframework.error.ExceptionExceptionHandler - An unhandled exception was thrown
com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) [Guice/ErrorInjectingConstructor]: NullPointerException: Cannot read field "oauthConfiguration" because "<parameter1>" is null
  at LogoutAction.<init>(LogoutAction.java:23)
  while locating LogoutAction
...
2023-09-22 12:02:13.714 PM ERROR org.primeframework.mvc.PrimeMVCRequestHandler - Error encountered
java.lang.NullPointerException: Cannot read field "actionURI" because "actionInvocation" is null

I thought maybe you had a typo and the Logout URL was supposed to be set to https://<MY_FUSIONAUTH_HOST>/account/logout?client_id=377e77a7-e066-4896-b3b4-5c2bbaec96e0 (rather than /account/logout/377e77a7-e066-4896-b3b4-5c2bbaec96e0) but that variant also led to a HTTP 500:

2023-09-22 12:06:07.062 PM ERROR io.fusionauth.app.primeframework.error.ExceptionExceptionHandler - An unhandled exception was thrown
com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) [Guice/ErrorInjectingConstructor]: NullPointerException: Cannot read field "oauthConfiguration" because "<parameter1>" is null
  at LogoutAction.<init>(LogoutAction.java:23)
  while locating LogoutAction
...
2023-09-22 12:06:07.064 PM ERROR org.primeframework.mvc.PrimeMVCRequestHandler - Error encountered
java.lang.NullPointerException: Cannot read field "actionURI" because "actionInvocation" is null

Am I doing something wrong with my steps, or is the proposed workaround faulty?

dransome avatar Sep 22 '23 12:09 dransome

For the record, the following workaround seems to be viable for my use case:

Assuming a single (real) application with ID: 5f921ed5-1700-4f25-8ec4-7db9b267c96a

  • Configure application logout links to point to https://<MY_FUSIONAUTH_HOST>/account/logout?client_id=5f921ed5-1700-4f25-8ec4-7db9b267c96a
  • Configure self-service theme with logout links pointed to <a href="${request.contextPath}/account/logout?client_id=${client_id}">
  • FusionAuth application (5f921ed5-1700-4f25-8ec4-7db9b267c96a) configuration:
    • Logout URL: my application's local logout URL (i.e. to trigger a logout of my application's local session)
    • Logout behavior: All applications (but basically irrelevant in my case, since we only have 1 application using FusionAuth at the moment).

Hope this helps someone else facing the same.

dransome avatar Sep 29 '23 17:09 dransome

@dransome apologies in the delayed response. I'm glad you got this working. The intent of the workaround was for the main application to call its logout endpoint like normal, and this action would trigger the side effect of invoking logout of "all applications" including the surrogate and self-service account applications.

We do have work in progress with a proper fix for this issue but I do not have an ETA for its delivery at the moment.

lyleschemmerling avatar Oct 02 '23 16:10 lyleschemmerling

@lyleschemmerling seems that my workaround creates its own problem.

We noticed intermittent HTTP 500 errors from the logout URL. (https://<MY_FUSIONAUTH_HOST>/account/logout?client_id=5f921ed5-1700-4f25-8ec4-7db9b267c96a).

My working theory is this happens if the user has not visited the FusionAuth self-service pages, and as a result, maybe FusionAuth doesn't find a session for that user?

When searching for logs, I found that it was the same error that I posted earlier. Is this just a result of what I'm doing with the workaround attempt(s), or a separate FusionAuth bug in its own right?

It seems that FusionAuth ought not to explode (HTTP 500) if you attempt to log out on a non-existent(?) session?

dransome avatar Nov 10 '23 13:11 dransome

Another option is to listen for refresh token revocation events and if a token for application 377e77a7-e066-4896-b3b4-5c2bbaec96e0is revoked, revoke the refresh token held for /account as well.

I don't believe the refresh token for account is visible. I tried to find it but was unable to see it on either the admin screen or the API. When I deleted all the sessions for a user using the 'delete all sessions' button in the admin UI, my account session remains alive.

It seems like front channel logout to https://<MY_FUSIONAUTH_HOST>/account/logout?client_id=... is the best option.

mooreds avatar Jul 12 '24 22:07 mooreds

This lost us a day+ of work because we were unaware that the profile management was its own session entirely and noticed issues when we were editing profiles and swapped users to see that we were seeing the prior user's data in the profile form.

The workarounds mentioned aren't tenable:

  • The /account/logout? call requires that the user visits the page hosted by the route to do signout. I've tried with fetch() calls and it doesn't sign out the user
  • Creating another app just to have this work is ridiculous

I wish there was an option to share the session between the profile management and standard SSO. We can't have users signing in again just to see or edit their profile.

The alternative for now is:

  • We have to implement the profile edit form ourselves using the user API. This is going to be frustrating because we also have to implement any validation rules around the fields and perform our own error handling.
  • We don't want to manage the reset password workflow ourselves, so we're going to instruct the user to sign out and request a password reset

I'm aware you can obtain a session to profile edit directly, but that requires that you send the Authorization header with a token to the FA profile page, which actually is difficult because the only way for this to work is to proxy the request (we initially thought we could use an iframe around the profile edit page), but then you run into so many other issues such as loading resources from the proxy origin, the form action doesn't submit to the right place, etc.

theogravity avatar Sep 28 '24 01:09 theogravity

Exactly my experience.

dransome avatar Sep 28 '24 08:09 dransome

This lost us a day+ of work because we were unaware that the profile management was its own session entirely and noticed issues when we were editing profiles and swapped users to see that we were seeing the prior user's data in the profile form.

The workarounds mentioned aren't tenable:

  • The /account/logout? call requires that the user visits the page hosted by the route to do signout. I've tried with fetch() calls and it doesn't sign out the user
  • Creating another app just to have this work is ridiculous

I wish there was an option to share the session between the profile management and standard SSO. We can't have users signing in again just to see or edit their profile.

The alternative for now is:

  • We have to implement the profile edit form ourselves using the user API. This is going to be frustrating because we also have to implement any validation rules around the fields and perform our own error handling.
  • We don't want to manage the reset password workflow ourselves, so we're going to instruct the user to sign out and request a password reset

I'm aware you can obtain a session to profile edit directly, but that requires that you send the Authorization header with a token to the FA profile page, which actually is difficult because the only way for this to work is to proxy the request (we initially thought we could use an iframe around the profile edit page), but then you run into so many other issues such as loading resources from the proxy origin, the form action doesn't submit to the right place, etc.

This has been a challenge for us as well, and something we have considered opening a support ticket for, as we are currently trying to decide if we should go for the "hosted" accounts page, or if we need to implement the entire thing our self. We really don't want to do it our selves, but the way this works as of now, the user experience is really not that good.

I think that when clicking a profile button from within any application, the user should be taken to were they can edit their information, and then, seamlessly, be taken back again to the application they came from to resume their doings.

Perhaps the account page could be in the form of a separate application, made entirely by FusionAuth, that can optionally be added within a Tenant, which shares it's session with the other applications within that Tenant, just like other applications? This application would be a bit like the one FusionAuth already provides for the Administrative stuff in the default Tenant. It would still need to be optional, and highly configurable, so we can restrict what information the users are able to edit and letting them administer MFA.

envopetter avatar Sep 30 '24 08:09 envopetter