Open Closed

Logout does not work consistently #7765


User avatar
0
alexander.nikonov created
  • ABP Framework version: v8.1.3
  • UI Type: Angular
  • Database System: EF Core (Oracle)
  • Auth Server Separated OpenID Server

I still can't get logout to work consistently in my Angular applications.

First of all, I commented the line - just for your information:

    {
        path: '',
        pathMatch: 'full',
        loadChildren: () => import('@my-home-page-package').then(m => m.MyHomeModule),
        // canActivate: [AuthGuard] // This was a logout blocker
    }

Now: A) If i use this code:

    @Injectable()
    export class MyServerErrorInterceptor implements HttpInterceptor {
      constructor(private router: Router) { }
      intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(
          catchError((error: HttpErrorResponse) => {
            if (error.status === 401) {
              this.router.navigate(['/']);
              return EMPTY;
            } else {
              return throwError(error);
            }
          })
        );
      }
    }

and click "Logout" link in the user menu, the logout seems to be working as expected from tab 1 in the browser. However if I have another page being open from the same site in tab 2 - it is not redirected to Login page: instead, I come up with an empty (no navigation menu) home page shown in this tab;

B) If i replace ['/'] with ['/login'] in the code above - both tabs react on this, however I end up with the endless loop, when both tabs try to do something and it never ends; Also - if i have only one tab of the application opened in the browser, I still have a weird situation, when after getting connect/revocat request (twice, btw - ??) and several 401 API requests, I see (so i see traditional openid-configuration and so forth), so I am not actually logged out - but redirected to a home page;

C) If i emulate the situation with a token expiration - removing "local storage" data in the browser, I expect to be redirected to Login page on the next request attempt (instead of giving me error 401). So the setting (B) provides this, however setting (A) redirects me to a no-menu home page;

And in general, logout process visually does not look good: I expect if a user clicks "Logout" - he is redirected to Login page straight away. Instead, I see the navigation menu is being disappeared and still observing a home page while some server operations are taking place;

I have some Middleware in my OpenID Server module to do cleaning work on httpContext.Request.Path.Value == "/connect/logout" scenario (B) (but works fine with scenario (A) - the exception does not happen). So I commented it, but it RANDOMLY affected the described scenario (sometimes it did, sometimes it did not):

    public async Task InvokeAsync(HttpContext httpContext, IAbxRequestContext requestContext, IdentityUserManager userManager)
    {
        await _next(httpContext);
    
        if (httpContext.Request.Path.Value == "/connect/logout")
        {
            await OnSessionEndRequestAsync(httpContext, userManager);
        }
    }
    
    private async Task OnSessionEndRequestAsync(HttpContext httpContext, IdentityUserManager userManager)
    {
        try
        {
            var user = await userManager.GetUserAsync(httpContext.User); //IF IT GETS INVOKED AND CRASHES - IT SEEMS TO AFFECT THE LOGOUT
            //Some cleaning routine - accessing DB, removing entries
        }
        catch(Exception ex)
        {
            _logger.LogError(ex, "Session End handling error");
        }
    }

Thus, I have no clue what might be a real root cause of the logout issue.

I kindly ask you to provide me with the correct settings (code) for a front-end and a back-end settings which might be relevant to the situation.

I cannot provide you with our code, sorry.

My suggestion is to start with a root cause of the exception in the call above. It is:

at System.Threading.CancellationToken.ThrowOperationCanceledException() at System.Threading.CancellationToken.ThrowIfCancellationRequested() at Volo.Abp.Identity.IdentityUserStore.FindByIdAsync(String userId, CancellationToken cancellationToken) at Castle.Proxies.IdentityUserManagerProxy.FindByIdAsync_callback(String userId) at Castle.Proxies.Invocations.UserManager1_FindByIdAsync.InvokeMethodOnTarget() at Castle.DynamicProxy.AbstractInvocation.Proceed() at Castle.DynamicProxy.AbstractInvocation.ProceedInfo.Invoke() at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue1.ProceedAsync() at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func3 proceed) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue1.ProceedAsync() at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)

whereas httpContext.User is OK (data is ok and it is authenticated).


57 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The js code will do the follow work:

    Both tabs will register a load event. If one of the tabs logs in to the website, it will write the stateKey, and then the other tab will receive the storage change event, and you can refresh or redirect to the homepage(/).

    Please ensure that the login and other pages have loaded this script

    (function () {
    
        const stateKey = 'authentication-state-id';
    
        window.addEventListener('load', function () {
            if (!abp || !abp.currentUser) {
                return;
            }
    
            if (!abp.currentUser.isAuthenticated) {
                localStorage.removeItem(stateKey);
            } else {
                localStorage.setItem(stateKey, abp.currentUser.id);
            }
    
            window.addEventListener('storage', function (event) {
    
                if (event.key !== stateKey || event.oldValue === event.newValue) {
                    return;
                }
    
                if (event.oldValue || !event.newValue) {
                    window.location.reload();
                } else {
                    location.assign('/')
                }
            });
        });
    
    }());
    
  • User Avatar
    0
    alexander.nikonov created

    Hi. Thank you. I have the window load handler both in the OpenID server module added via AbpBundlingOptions and the same logic added via app.component.ts constructor like this (the load handler code DOES get triggered when I load Angular app pages):

        this.document.defaultView.addEventListener('storage', event => {
          if (event.key === 'access_token' && event.newValue === null) {
            this.document.defaultView.location.reload();
          }
        });
        const stateKey = 'authentication-state-id';
        const onLoad = () => {
          if (!this.currentUser.isAuthenticated) { // this.configStateService.getOne('currentUser')
            localStorage.removeItem(stateKey);
          }
          else {
            localStorage.setItem(stateKey, this.currentUser.id);
          }
          this.document.defaultView.addEventListener('storage', (event) => {
            if (event.key !== stateKey || event.oldValue === event.newValue) {
              return;
            }
            if (event.oldValue || !event.newValue) {
              this.document.defaultView.location.reload();
            }
            else {
              location.assign('/');
            }
          });
        };
        if (this.document.readyState === 'complete') {
          onLoad();
        }
        else {
          this.document.defaultView?.addEventListener('load', onLoad);
        }
        
    

    However, the error 400 is still there (with the same "The provided antiforgery token was meant for a different claims-based user than the current user." exception message): when I click "Login" button in OpenID server web page in the passive tab - neither code of authentication-state-listener.js is invoked (I put the breakpoints everywhere).

    What am I doing wrong?

  • User Avatar
    0
    alexander.nikonov created

    Any update?

  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Expert

    Thank you for your patience, and I apologize for the delayed response. I have carefully revisited your concern in light of the latest updates. Based on the analysis, the logs and debugs are not triggered as the errors in question are likely occurring earlier in the process.

    Here is a detailed explanation of the issue:

    1. 400 Bad Request Error:

      This error indicates that the request to the identity provider (IdP) was unsuccessful. Common causes include:

      • Expired sessions.
      • Incorrect or missing parameters.
      • Validation issues on the IdP side.

      When this error occurs, the OpenID Connect library is unable to complete the authentication flow, leading to a secondary error.

    2. Recommended Approach:

      To achieve a clearer response from the library, consider reviewing the discussion here and implementing the solution described in this sample project.

    3. invalid_nonce_in_state Error:

      As illustrated in the attached example, this secondary error occurs when the library detects an incomplete or invalid flow and securely terminates it to prevent further processing with incorrect or missing information.

      This behavior is by design and serves as a safeguard, ensuring that broken authentication attempts are securely halted. It is not indicative of a bug, but rather a critical feature to maintain security. However, this may have downstream impacts on your business logic, so adjustments might be necessary to account for this scenario.

  • User Avatar
    0
    alexander.nikonov created

    Reopening. Thank you for your response. Unfortunately, I am currently overwhelmed with my existing tasks and do not have the time to analyze your reply at the moment.

  • User Avatar
    0
    alexander.nikonov created

    @sumeyye.kurtulus thank you for the reply. Whereas I cannot handle this issue now and I did not dig into the suggested project, I have a quick question for you: will it be easy to integrate the authenticate service solution by your link into our ABP-based solution? In fact, I have no idea how it works "under the hood" now, because we just use a standard AuthService from @abp/ng.core to log out and it is place in a separate NPM component. No custom logic attached. So what is it supposed to be at the very end? Replacing AuthService of ABP with the customized version from the mentioned project via providers?

  • User Avatar
    0
    masum.ulu created
    Support Team Angular Expert

    Hi alex,

    The problem is not exactly on client side, server is not allows you to authenticate at the same time with multiple tabs. We'll handle this behavior on client side in someway yet, this very specific case for the occur. We'll try to handle this error with specificed code or content. You might handle this problem with error handling maybe custom interceptor can help with that until we fix

Made with ❤️ on ABP v9.1.0-preview. Updated on December 13, 2024, 06:09