Async logout and redirect to /login issue

I have situation where an async call may force a logout. Upon logging out, the user should be directed to the login page. This is the code I use:

    ui.getSession().getSession().invalidate();
    ui.getSession().close();
    SecurityContextHolder.clearContext();
    ui.getPage().setLocation("/login");

The logout is successful, but the redirect to /login doesn’t work. I have tried to call “setLocation(”/login")" before “invalidate()” and “close()”, wrap it in ui.access(), etc., but the result is the same - nothing happens. There are no errors either.

I’m using Vaadin 24.3.13. Is the code I use some outdated way to logout and navigate to login, or what could be the issue?

I would suggest to updated to a recent version of Vaadin, e.g. 24.5.6 (if you have to stick with 24.3, upgrade at least to 24.3.20) and to use AuthenticationContext.logout() along with VaadinWebSecurity to configure Spring Security.

https://vaadin.com/docs/latest/flow/security/enabling-security#security-utilities
https://vaadin.com/docs/latest/flow/security/enabling-security#security-configuration-class

I use AuthenticationContext on a logout button and it works fine there. I remember trying to use it for the async call, but, unless I remember wrong, I got NPEs one some of the singleton calls in the class:

public void logout() {
    HttpServletRequest request = VaadinServletRequest.getCurrent()
            .getHttpServletRequest();
    HttpServletResponse response = VaadinServletResponse.getCurrent()
            .getHttpServletResponse();
    Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();
    ... ...
}

I can try use it again and update Vaadin version. See if either fixes the issue.

NPE in logout should have been fixed by fix: allow null response in logout by tltv · Pull Request #20057 · vaadin/flow · GitHub
The change has been back-ported, also to the last 24.3 version.
Please, note that Vaadin 24.3 is not supported anymore.

Hmm. AuthenticationContext still doesn’t work in my situation, but after updating Vaadin version to 24.5.6 the original code I posted works, though there is a second or a few delay until the redirecting happens.

Perhaps the way I do the async calls and error handling is wrong, I might be missing something. I tried to create a barebone version of what I’m trying to do async. Basically, two async calls are done and joined, and at some point during this there is an error. The error should cause a logout. This is the example code:

private void fetchData(UI ui) {
    // Simulate async tasks with delays
    CompletableFuture<String> async1 = getAsync("1");
    CompletableFuture<String> async2 = getAsync("2");
    // Combine futures and handle results
    CompletableFuture<Void> allFutures = CompletableFuture.allOf(async1, async2);
    allFutures.thenRun(() -> {
        throw new IllegalStateException("Simulate error");
    }).exceptionally(throwable -> {
        try {
            System.out.println("Error during data fetching");
            // Logout with authenticationContext does not work:
            ui.access(authenticationContext::logout);
            // Logout less clean but works:
            ui.access(() -> {
                ui.getSession().getSession().invalidate();
                ui.getSession().close();
                SecurityContextHolder.clearContext();
                ui.getPage().setLocation("/login");
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    });
}

private CompletableFuture<String> getAsync(String data) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            return data;
        } catch (InterruptedException e) {
            throw new RuntimeException("Async operation interrupted", e);
        }
    });
}

Do AuthenticationContext.logout throw some exception? I wonder if the problem is that the Spring security context holder thread local is not correctly set, since the code is running in a thread that is not “Spring aware”

https://docs.spring.io/spring-security/reference/features/integrations/concurrency.html

I get the stacktrace below when calling “ui.access(authenticationContext::logout);”. I’m new to spring so it could absolutely be that I haven’t set the spring security context correctly. I threw something together to make it work, this is basically how I do it:

private void setSecurityContext(UsernamePasswordAuthenticationToken authenticationToken) {
    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);

    HttpSession session = httpServletRequest.getSession(true);
    session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
}
java.util.concurrent.ExecutionException: java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.server.VaadinServletRequest.getHttpServletRequest()" because the return value of "com.vaadin.flow.server.VaadinServletRequest.getCurrent()" is null
	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) ~[na:na]
	at com.vaadin.flow.server.FutureAccess.get(FutureAccess.java:62) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2162) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.server.VaadinSession.unlock(VaadinSession.java:755) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.server.VaadinService.ensureAccessQueuePurged(VaadinService.java:2123) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.server.VaadinService.accessSession(VaadinService.java:2090) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.server.VaadinSession.access(VaadinSession.java:1059) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.component.UI.access(UI.java:570) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.component.UI.access(UI.java:553) ~[flow-server-24.5.7.jar:24.5.7]
	at com.application.TestView.lambda$fetchData$5(TestView.java:270) ~[classes/:na]
	at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:990) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:974) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188) ~[na:na]
Caused by: java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.server.VaadinServletRequest.getHttpServletRequest()" because the return value of "com.vaadin.flow.server.VaadinServletRequest.getCurrent()" is null
	at com.vaadin.flow.spring.security.AuthenticationContext.doLogout(AuthenticationContext.java:161) ~[vaadin-spring-24.5.7.jar:na]
	at com.vaadin.flow.spring.security.AuthenticationContext.logout(AuthenticationContext.java:155) ~[vaadin-spring-24.5.7.jar:na]
	at com.vaadin.flow.component.UI.accessSynchronously(UI.java:498) ~[flow-server-24.5.7.jar:24.5.7]
	at com.vaadin.flow.component.UI$2.execute(UI.java:573) ~[flow-server-24.5.7.jar:24.5.7]
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
	at com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2159) ~[flow-server-24.5.7.jar:24.5.7]
	... 17 common frames omitted

Ah, yeah. AuthContext.logout() is called in a UI.access() command from a background thread, so VaadinRequest thread local is not available, and the request is required to execute Spring logoutSuccessHandler.
So, this is effectively not an option if you want to logout from a background thread.

I wonder if you can make it work by postponing the logout to a client initiated request.
Something like

        ui.access(() -> {
            ui.getPage().executeJs("return true").then(ignored -> {
                authenticationContext.logout();
            });
        });

Yeah, that works. Perhaps I ask a bit stupidly now, but is this a safe way to ensure a logout? I don’t know exactly what happens here, I just want to make sure that the server logs out the user without it going through the client first → the execute js part concerns me :slight_smile:

The above snippet tells the client to do a server call, and when the request reaches the server it performs the logout.

Yeah that’s the concern. Technically the client could dismiss the request, and then the user stays logged in?

Yes, there’s a chance for this to happen. If you want to invalidate the session immediately, you have to go with the other solution. Just be aware that Spring logout success handler will not be invoked.

OK. Just curious, is it important to trigger the logout success handler(s)? Are there any default/under the hood handlers that should be triggered, or are the handlers just something that I can chose to add if I want to use the spring logout mechanism? I assume it’s handlers added in the config:

@EnableWebSecurity
@Configuration
public class SecurityConfig extends VaadinWebSecurity {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout(httpSecurityLogoutConfigurer → httpSecurityLogoutConfigurer
.addLogoutHandler((request, response, authentication) → {}));

Considering that I call “SecurityContextHolder.clearContext();” instead.

Can’t say. I think it depends on the application configuration. By default, it probably only redirects to the login page.
You should also make sure that executing SecurityContextHolder.clearContext() behaves as expected in a background thread, according to the documentation link I posted above.

Yeah, good point. I don’t want to accidentally logout the wrong user or cause other unwanted behaviors. Thanks :+1: