Error on logout

Hi everyone, i have a strange error message upon logout.

I cant find the issue due to no error log or any other hint gets thrown. Does anyone has a clue?

I am using the JWT login from the documentation but the crux is that i need two login views and i think there must be a miss config anywhere…

Here is my SecurityConfig

SecurityConfig.java

`EnableWebSecurity
@Configuration
@Order(4)
public class SecurityConfigFrontend extends VaadinWebSecurity {
@Autowired
private UserDetailsService userDetailsService;

@Autowired
LoginAttemptService loginAttemptService;

@Autowired
AzureKeyVaultService azureKeyVaultService;

@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;

@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;


private static final String[] PUBLIC_ENDPOINTS = {

        "/images/**",
        "/application/health/**",
        "/swagger-ui/**",
        "/v3/**",
        "/css/**",
        "/js/**",
        "/font-awesome/**",
        "/img/**",
        "/fonts/**",
        "/VAADIN/**",
        "/frontend/**",
        "/webjars/**",
        "/error"

};

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
                    // Öffentliche statische Ressourcen
                    .requestMatchers(Arrays.stream(PUBLIC_ENDPOINTS)
                            .map(path -> AntPathRequestMatcher.antMatcher(HttpMethod.GET, path))
                            .toArray(AntPathRequestMatcher[]::new)).permitAll()

                    // Login-Seiten für beide Rollen
                    .requestMatchers("/", "/admin/login", "/customer/login", "/customer/logout", "/admin/logout").permitAll()

                    // Alle anderen Anfragen erfordern Authentifizierung
                    .anyRequest().authenticated()
            )

            .exceptionHandling(exception -> exception
                    .accessDeniedHandler(accessDeniedHandler)
                    .authenticationEntryPoint(customAuthenticationEntryPoint)
                    .accessDeniedPage("/access-denied")
            )

            // CORS und JWT-Filter
            .cors(withDefaults())
            .addFilterBefore(new JwtAuthenticationFilter(azureKeyVaultService), UsernamePasswordAuthenticationFilter.class);

    // Custom Authentication Provider
    http.authenticationProvider(customAuthenticationProvider());


    http.csrf(csrf -> csrf.disable());
	
    // JWT-Konfiguration für Vaadin
     setStatelessAuthentication(
             http,
             new SecretKeySpec(
                     Base64.getDecoder().decode(azureKeyVaultService.getSecret(AzureKeyEnum.VAADIN_JWT_KEY)),
                     JwsAlgorithms.HS256
             ),
             "blabla",
             86400
     );
}

@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, CustomAuthenticationProvider customAuthProvider) throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
            .authenticationProvider(customAuthProvider)
            .build();
}

@Bean
public CustomAuthenticationProvider customAuthenticationProvider() {
    return new CustomAuthenticationProvider(userDetailsService, encoder(), loginAttemptService, azureKeyVaultService);
}

@Bean("authProvider")
public DaoAuthenticationProvider authProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(encoder());
    return authProvider;
}

@Bean(name = "encoder")
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder();
}

}`

From the following view i have one for customer and one for admin (m ust be two seperated views) This View extends BaseLoginView which is the implementation of LoginView Details and adds the loginform

LoginView

`@Slf4j
@RoutePrefix(Customer)
@Route(“login”)
@PageTitle(“Customer Login”)
@AnonymousAllowed
@PermitAll
public class CustomerLoginView extends BaseLoginView {

public CustomerLoginView(LoginAttemptService loginAttemptService, CaptchaService captchaService, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder) {
    super(loginAttemptService, captchaService, authenticationManager, passwordEncoder);
}

@Override
protected void onSuccessfulLogin(String username, Authentication authentication) {
    SecurityContextHolder.getContext().setAuthentication(authentication);

    Object principal = authentication.getPrincipal();
    if (principal instanceof User user) {
        if (user.getUserType() == UserType.Customer) {
            loginAttemptService.loginSucceeded(username);
            UI.getCurrent().navigate("/Customer/dashboard");
            return;
        }
        loginAttemptService.loginFailed(username);
        login.setEnabled(true);
        NotificationUtil.error("Login fehlgeschlagen. Sie haben keine Berechtigung für das Customer-Portal.");
    }
}


@Override
protected String getPortalTitle() {
    return "Customer Portal";
}

@Override
protected String getBackgroundImagePath() {
    return "/images/Customerportal/login-image.jpg";
}

}
`

Logout Method

` public void logout() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

    if (!(auth instanceof JwtAuthenticationToken token)) {
        // Kein gültiger Token vorhanden – einfach zur Startseite weiterleiten
        UI.getCurrent().getPage().setLocation("/");
        return;
    }

    Collection<? extends GrantedAuthority> authorities = token.getAuthorities();

    boolean isSupplier = authorities.stream()
            .anyMatch(authz -> "ROLE_CUSTOMER".equals(authz.getAuthority()));
    boolean isInsurer = authorities.stream()
            .anyMatch(authz -> "ROLE_ADMIN".equals(authz.getAuthority()));

    // Session schließen, wenn vorhanden
    VaadinSession session = VaadinSession.getCurrent();
    if (session != null) {
        session.getSession().invalidate();
        session.close();
    }

    // Security-Kontext leeren
    SecurityContextHolder.clearContext();

    if (authenticationContext != null) {
        authenticationContext.logout();
    }

    // Optional: Logging
    System.out.println("User " + token.getName() + " logged out.");

    // Weiterleitung je nach Rolle
    String redirectUrl = "/login";
    if (isSupplier) {
        redirectUrl = "customer/logout";
    } else if (isInsurer) {
        redirectUrl = "admin/logout";
    }

    UI.getCurrent().getPage().setLocation(redirectUrl);
}`

I now investigated it a little more:

The issue is regarding authorization i think.

on the first login the SecurityContext holds a UserDetails Object, once i switch to antother page it holds a JwtAuthToken Object. but the JWT doesnt containe a ROLE_CUSTOMER thats why it wants to redirect to “/login” which not exists.

Is there a way to add a claim to the JWT which is generated upon login? I dont want to set the ROLE_CUSTOMER because our roles are for views. This claim should represent more the type of a user (customer, seller, admin etc)

It looks like you have an almost custom security implementation, since you’re not calling super.configure().
You may want to take a look at VaadinWebSecurity and check how the UIDL redirect strategy is set