Starts in:
1 DAY
22 HRS
40 MIN
57 SEC
Starts in:
1 D
22 H
40 M
57 S
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).


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

    Hi,

    how do I reproduce the problem,

    i could not reproduce it.

    my steps

    • login
    • open users page
    • logout

  • User Avatar
    0
    alexander.nikonov created

    I really don't know how to match a test solution and our solution where the issue happens. I've tried to find who initiates these extra API calls between connect/revocat and openid-configuration looking at the initiator chain: Just in case: port 44337 is the port of Ocelot Gateway solution - I emphasize that the last endpoint is correct. Is the request initiator chain looking wrong here? Because so far I am out of clues, what is wrong and why. Maybe I am missing some settings relevant for authentication or they are incorrect? There are just too many of them, plus custom code, so I try to move from inside analyzing those requests.

    IMPORTANT: this is not related to our custom pages only. The identical situation take place at ABP pages, e.g. Language Management. So it is not related to the components or guards.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    IMPORTANT: this is not related to our custom pages only. The identical situation take place at ABP pages, e.g. Language Management. So it is not related to the components or guards.

    There are no additional requests here

  • User Avatar
    0
    alexander.nikonov created

    When I am doing a logout in our project from ANY page - at some point I promptly see a HOME page, but there is no more navigation menu at the left (the menu obviously has been hidden, because the user is not authenticated anymore) and at the same very moment the API requests which are connected to that ANY page are invoked. Afterwards I am redirected to LOGIN BOX as expected.

    I have absolutely no idea why the redirect to LOGIN BOX does not happen immediately after pressing the logout button and this HOME page is shown. I have looked through the code as much as I can. I can only attach again the Auth Server Module code and its log - what is happening at the moment of logout. I compared the log with the test project log, but by and large everything is about the same.

    Perhaps one of your colleagues can tell me something - maybe there is a wrong setup in the module. But there are so many settings that it's hard to make sense of it. I can create another ticket, but it will contain the same question.

    I cannot do screensharing or send our project. Just reminding it.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Sorry for being late. I will let the Angular team answer your question.

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

    Hello, I have also been checking by following the steps that you have provided during the conversation. However, I could not reproduce any of the findings.

    So, my first question would be whether you just want to overcome the network errors. If this is the case, you should be checking your service functionalities since we do not trigger anything extra among these processes. At this point, I want to remind you the fact that we send two different requests for connect/revocat to revoke both tokens as being access and refresh. If this is not your case and you just want to redirect user to the login or home page, that is discussable.

    Also here is what I experience while logging out as you have explained.

  • User Avatar
    0
    alexander.nikonov created

    Hi,

    thank you for the reply. So - first of all, I want to eliminate the following problem which is easily reproducable on a test project.

    If I have the site opened in tab #1 and tab #2 and use the following code in app.component.ts:

    this.window.addEventListener('storage', event => {
      if (event.key === 'access_token' && event.newValue === null) {
        this.window.location.reload();
      }
    });
    

    after logging out in any tab, I am logged out in another tab thanks to this code. So far so good...

    Now, let's say I log in on tab #1 - I am inside. Then I go to tab #2 where I still have a login box. I try to log in and get the exception:

    [17:04:41 INF] Executing endpoint '/Account/Login' [17:04:41 INF] Route matched with {page = "/Account/Login", area = "", action = "", controller = ""}. Executing page /Account/Login [17:04:41 INF] Skipping the execution of current filter as its not the most effective filter implementing the policy Microsoft.AspNetCore.Mvc.ViewFeatures.IAntiforgeryPolicy [17:04:41 INF] Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user. at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)

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

    Hello, I believe I had reproduced the same problem. The issue might be related to this condition event.newValue === null as can be seen in the record.

  • User Avatar
    0
    alexander.nikonov created

    Unfortunately, this is not a solution, because if I make this change, another part is broken: I am no more redirected to Login page in a passive tab after I log off in the active tab...

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

    I wish this could have helped you, but it seems that there’s an aspect of the configuration that I’m not fully knowledgeable about to resolve the issue. From what I understand of your setup, it appears that the problem is not on our side. I understand that the proposed change affects the redirection to the login page in a passive tab after logging off in the active tab, and I apologize for any inconvenience this may cause.

  • User Avatar
    0
    alexander.nikonov created

    Hi. Sorry, I'm not sure I am following you here. The latest mentioned problem is reproduced on ABP generated test project. There's no our custom code there. So in this project i was trying to get the following behavior:

    1. when I do logout from the active tab, I'm being logged off on any passive tab of the same site: the solution with storage event was suggested by your colleague and it did work;
    2. when I do log in in a passive tab after being logged in in the active tab I want to avoid any errors. You suggested to change null condition from === to !== in the event handler, but in this case (1) stops working - I'm not redirected to Login page in a passive tab anymore;
  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Expert

    Hello, I apologize if we were unable to assist you effectively. It seems that we were also not able to fully replicate the issue on our side. To better understand and resolve the problem, it would be extremely helpful if you could provide a sample project through an email (sumeyye.kurtulus@volosoft.com) that clearly demonstrates the issue.

    We greatly appreciate your cooperation and thank you for your understanding.

  • User Avatar
    0
    alexander.nikonov created

    The test project link is already present in this thread. https://drive.google.com/open?id=1xYCu_NLl5O0YCn1h3ffv4FZd5UGkGK0B&usp=drive_fs

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

    I have tested the app you provided, and it appears to be working as expected. I didn’t notice anything that might disrupt the process on the frontend, as demonstrated in this GIF.

  • User Avatar
    0
    alexander.nikonov created

    Sorry, but I easily reproduce the issues on the same test project. Please have a look below. I don't know how to explain it.

    1. Scenario with event.newValue !== null - the second tab does not redirect a user to Login page when I do logout from the first tab:
    2. Scenario with event.newValue === null - the second tab redirects a user to Login page when I do logout from the first tab, however the bug with a token is reproduced during logging in:
  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Expert

    I get your point, but this should be something not related to these conditions as you may also see from these records with these relative conditions:

    1. event.newValue !== null

    2. event.newValue === null

    3. No condition is provided

  • User Avatar
    0
    alexander.nikonov created

    Your screenshots are now consistent with mine. So scenarios 1) and 3) do not fit me. Because the "passive" tab does not redirect to login box. Scenario 2) is what I need. But in your screenshot you did not go further: you need to try to login in the "passive" tab too. In this case, you will get error 400 because of the "reused token" issue. This is what I am trying to avoid.

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

    I understand your point now. Could you share your SignalR requests as well?

  • User Avatar
    0
    alexander.nikonov created

    SignalR? Hmm, in the test project (from the attachment) I don't use SignalR. But in our main solution we do use it. If it comes in handy to idenfity a working solution for logout, here it is:

    It's a typical SignalR configuration, I guess...

  • User Avatar
    0
    alexander.nikonov created

    Any update here?

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

    I apologize for the delay in responding. We have reviewed the issue independently of the provided steps and encountered a similar problem. I will be discussing this with my team and will provide an update shortly. Thank you for your patience and cooperation.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    In this case, you will get error 400 because of the "reused token" issue. This is what I am trying to avoid.

    2024-10-23 11:09:42.807 +03:00 [INF] Antiforgery token validation failed. The antiforgery cookie token and request token do not match.
    Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery cookie token and request token do not match.
       at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
       at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
    2024-10-23 11:09:42.809 +03:00 [INF] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter'.
    2024-10-23 11:09:42.813 +03:00 [INF] Executing StatusCodeResult, setting HTTP status code 400
    

    This is the AntiforgeryToken error.

    When you log in in tab-a, your cookies will be refreshed, and the old antiforgery token is still saved in tab-b HTML from.

    see https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-8.0#multiple-browser-tabs-and-the-synchronizer-token-pattern

  • User Avatar
    0
    alexander.nikonov created

    https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-8.0#multiple-browser-tabs-and-the-synchronizer-token-pattern

    Ok - thank you for this link.

    In our case the message is different - "Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user." - but I think it's due to the same issue.

    So the site tells "Consider alternative CSRF protection patterns if this poses an issue." - but nothing specific.

    I think in the given situation I could just redirect a user (already authenticated in the first tab) to the initial (home) page in the second tab, if such situation takes place instead of trying to log him in again. I just need the hint where I should place the corresponding check, please.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user.

    Yes, this is because you have logged in to another tab.

    I think in the given situation I could just redirect a user (already authenticated in the first tab) to the initial (home) page in the second tab, if such situation takes place instead of trying to log him in again. I just need the hint where I should place the corresponding check, please.

    We have added a new js to refresh the page if authentication changes. You can add this js file to your 8.1.3 version.

    https://github.com/abpframework/abp/pull/19569

  • User Avatar
    0
    alexander.nikonov created

    We have added a new js to refresh the page if authentication changes. You can add this js file to your 8.1.3 version.

    I've tried this file and made sure that it has been added to the markup. The following piece of code has been added to OpenID AbpModule:

        Configure<AbpBundlingOptions>(options =>
        {
            ...
            options.ScriptBundles.Configure(
                StandardBundles.Scripts.Global,
                bundle =>
                {
                    bundle.AddFiles("/libs/abp/aspnetcore-mvc-ui-theme-shared/authentication-state/authentication-state-listener.js");
                }
            );
        });
    

    But it did not affect the issue in any way.

    I've placed the breakpoints inside authentication-state-listener.js and its code has not been invoked during the login process in the passive tab (after a user has already logged-in in the active tab). I think this code is not relevant - instead, there has to be reaction on "Login" button click (i.e. redirect a user to Home page instead of trying to authenticate him), etc.

    Please be noted that I had to retain the code you suggested in the very beginning to automatically redirect user to Login box in the passive tab (this is placed in the constructor of app.component.ts:

      this.window.addEventListener('storage', event => {
        if (event.key === 'access_token' && event.newValue === null) {
          this.window.location.reload();
        }
      });
    
Made with ❤️ on ABP v9.1.0-preview. Updated on November 20, 2024, 13:06