vaadinvaadin-flowvaadin10

Vaadin 14.2: Intercept logout (invalidate session) with BeforeLeaveListener


in my Vaadin 14.2.0 application there is a BeforeLeaveListener to show a confirmation dialog when the input is dirty (= there are unsaved changes in the input fields) to cancel (or intentionally proceed) the navigation:

BeforeLeaveListener listener = new BeforeLeaveListener() {
    @Override
    public void beforeLeave(BeforeLeaveEvent event) {
        if (dirtyFlag.isDirty()) {
            ContinueNavigationAction postponeAction = event.postpone();
            // show confirmation dialog and maybe execute a proceed
            [...] () -> postponeAction.proceed();
        }
    }
};
UI.getCurrent().addBeforeLeaveListener(listener);

This works fine for everything but for the logout. This is how my logout button looked like in the start (what works as long as I do not want to postpone/cancel the logout):

Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
    VaadinSession.getCurrent().getSession().invalidate();
    UI.getCurrent().navigate(LoginView.class);
});

Now I want to postpone the logout until the user confirms that the unsaved changes should get discarded. (Switching both lines in the EventListener seems to be a good idea, but did not work because of the following.) Within the navigate-call the BeforeLeaveListener is called (good), but the logout is done nevertheless, because the code after the navigate()-call is executed (of course, because it is no blocking call), the session is invalidated and the user is logged out though the confirmation dialog just popped out (but the user had no chance to confirm/deny).

I tried to move the session invalidation into the LoginView (into a BeforeEnterObserver), but the result is that the login view is reloaded in an endless loop.

Question: is there something like "navigate to LoginView and if navigation is not postponed and not cancelled, then invalidate session"?

A workaround is to navigate to an intercepting LogoutView that just invalidates the session and redirects/forwards to the LoginView:

@Route(value = "logout")
public class LogoutView implements BeforeEnterObserver {
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        VaadinSession.getCurrent().getSession().invalidate();
        event.forwardTo(LoginView.class);
    }
}

But this seems to me to be just a bad workaround with some overhead (creating a view just to forward to another view)...


Solution

  • I know, you didnt mention Spring. But that's actually what I believe Spring Security is doing under the hood.

    My Logout Button (using Spring Security) is looking like this:

    new Button("Logout", click -> {
        UI.getCurrent().getPage().executeJs("location.assign('logout')");
    });
    

    Once you click it, you are logged out and redirected to the login view (more details from Baeldung). I think it will be just fine if you do your LogoutView redirection detour.

    The fact that you can even use the Navigator to go to your LogoutView is even better here, since this is caught by BeforeLeaveObserver (in contrast to completely new requests by assigning a new location or refreshing the page. See BeforeLeaveObserver vs. beforeunload for more details on that)


    I would still like to propose another idea for your usecase. I think it will be much simpler, but requires that the logout link knows of the dirtyFlag:

    Anchor link = new Anchor();
    link.getElement().addEventListener("click", e -> {
        if(dirtyFlag.isDirty()){
            // show Dialog with 
            // - a confirm btn that calls doLogout() on click
            // - a cancel btn that closes the Dialog
        } else {
           doLogout();
        }
    });
    
    ...
    
    private void doLogout(){
        VaadinSession.getCurrent().getSession().invalidate();
        UI.getCurrent().navigate(LoginView.class);
    }