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).


33 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    could you share a minimum reproducible project with me? i will check it.

    shiwei.liang@volosoft.com

  • User Avatar
    0
    alexander.nikonov created

    I don't think this idea would work. We have a customized solution and we use a different DBMS, not MS SQL Server. So I might even not be able to reproduce it on a test ABP project.

    Anyway, I am ready to give it a go.

    So I am trying to set up a test project - I generated it from "abp suite 8.1.3". But the Migrator throws the exception about the connection string format (I am going to use my free MS SQL Server Express for test purposes).

    This is what has been generated by Abp Suite: "Data Source=Test;Integrated Security=yes;" - here the exception tells about unsupported "Integrated Security" parameter.

    I've change the format to "Server=MyServerAddress;Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=true" - but this format is not undestood also.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    you can remove MultipleActiveResultSets

  • User Avatar
    0
    alexander.nikonov created

    I removed, it tells now

    System.ArgumentException: ''Server' is an invalid connection string attribute

    So I don't understand what format it wants then.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    See https://www.connectionstrings.com/

  • User Avatar
    0
    alexander.nikonov created

    To me it looks like is it something wrong with ABP test project setup. Because I use the very same format string for my other .NET project and it runs fine. And your link also suggests the same string format I use.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    i don't think so, abp use the standard database library.

    btw, if you share a minimum reproducible project with me. i will check it.

  • User Avatar
    0
    alexander.nikonov created

    i don't think so, abp use the standard database library.

    btw, if you share a minimum reproducible project with me. i will check it.

    I've finally created a test project and changed the front-end port from 4200 to 4400 not to confuse our development apps with ABP framework test app. I've replaced '4200' with '4400' in all back-end json settings and environment.ts in front-end. I also updated it here:

    Though, I still cannot login to the Angular app:

    error:invalid_request error_description:The specified 'redirect_uri' is not valid for this client application. error_uri:https://documentation.openiddict.com/errors/ID2043

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    you may need to clear the Redis cache.

  • User Avatar
    0
    alexander.nikonov created

    It helped and I opened two tabs in the same browser of the test app. When I do logout in one tab (and being taken to the Login page) - another tab remains intact, until I try to navigate somewhere from it: then it redirects me to the Login page too.

    In our case, when I do logout on one tab - another tab is being affected too (so the navigation menu is being disappeared for the unauthorized user as expected, however the common layout remains). I was unable to determine what might trigger this. I can't send the code or do a screenshare. But probably you may give me some hints? Could it be an established SignalR connection, for instance? Or some guards?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    sorry, i have no idea

  • User Avatar
    0
    alexander.nikonov created

    What is a reliable way to prevent triggering API requests after I click logout on the site globally (without introducing many changes)? I am getting API requests from my page sent after clicking "Logout" link, but before being redirected to "Login" page. Of course such requests end up with error 401. But I do not want them to be triggered at all (i.e. I'd like to cancel the subscription of all of them in some centralized way). Is it tracking event type 'logout' inside HttpInterceptor? I've tried it and still see the same requests...

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    You can use Local storage events

    for example:

    this.window.addEventListener('storage', event => {
      if (event.key === 'access_token' && event.newValue === null) {
        this.window.location.reload();
      }
    });
    
  • User Avatar
    0
    alexander.nikonov created

    I've placed it in the ngOnInit() of my app.component.ts, but this.window.location.reload(); block has not been invoked during a logout...

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    you can try put debugger to check it

    this.window.addEventListener('storage', event => {
      debugger
      if (event.key === 'access_token' && event.newValue === null) {
        this.window.location.reload();
      }
    });
    
  • User Avatar
    0
    alexander.nikonov created

    Never triggered, thus - no any kind of 'storage' event...

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    it works for me

    import { inject, NgModule } from '@angular/core';
    import { PageModule } from '@abp/ng.components/page';
    import { SharedModule } from '../shared/shared.module';
    import { HomeRoutingModule } from './home-routing.module';
    import { HomeComponent } from './home.component';
    import { DOCUMENT } from '@angular/common';
    
    @NgModule({
      declarations: [HomeComponent],
      imports: [SharedModule, HomeRoutingModule, PageModule],
    })
    export class HomeModule {
      
      protected readonly window = inject(DOCUMENT).defaultView;
    
      constructor(){
        this.window.addEventListener('storage', event => {
          alert(event.key)
          if (event.key === 'access_token' && event.newValue === null) {
            this.window.location.reload();
          }
        });
      }
    }
    
    

  • User Avatar
    0
    alexander.nikonov created

    I've tried it on our solution, then on the generated ABP test solution. Still does not work. The invocation was only several times while creating tokens, etc. Not when logging out. Please find the attached.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    it works for me

  • User Avatar
    0
    alexander.nikonov created

    Hmm, it actually works - I checked the first tab, but the breakpoint is triggered for the "passive" tab. Thank you very much. Using this approach, I was able to get to the login page on both tabs. But it still does not solve the problem with sending API requests in my case - they still get sent at some point before the logout is complete:

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    you can redirect instead of reload

  • User Avatar
    0
    alexander.nikonov created

    Hi,

    you can redirect instead of reload

    Whereas it might work for a passive tab - it won't work for an active tab, where I click "Logout": because this code is not triggered for an active tab. However the API requests (mentioned in the screenshot) are invoked on both tabs.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    active tab no need to trigger this code; it has actively logged out

  • User Avatar
    0
    alexander.nikonov created

    I understand that active tab is not supposed to trigger this code, I just make a note that this:

    is happening during logout on active tab too - in any component (which i want prevent somehow with a general approach for all components).

Made with ❤️ on ABP v9.0.0-preview Updated on September 19, 2024, 10:13