Problems configuring Hilla for OAuth2, Stateless and i18n

I try to configure Vaadin Hilla to use OAuth2 for authentication (Authorization Code Flow) and when authentication completes succesfully, switch to stateless mode with JWT tokens (without session/JSESSIONID cookie). Setup also includes the fairly new Hilla i18n feature.

Idea behind this setup is to be able use an external load balancer which would stick client to one backend server for the duration of Authorization Code Flow (sticked by JSESSIONID) and after that, load balancer routes requests to any backend server with the JWT token.

With this combination I have problems (for example when I hit browser reload button):

  • When not authenticated, the i18n.configure() triggered DefaultBackend.loadTranslations call reaches TranslationFileRequestHandler.class and everything is fine
  • When authenticated, i18n.configre() ends up prematurely with SessionExpiredException (most of the time).

Exception is in VaadinService.class:

public void handleRequest(VaadinRequest request, VaadinResponse response)
		throws ServiceException {
	requestStart(request, response);

	VaadinSession vaadinSession = null;
	try {
		// Find out the service session this request is related to
		vaadinSession = findVaadinSession(request);
		if (vaadinSession == null) {
			return;
		}

		for (RequestHandler handler : getRequestHandlers()) {
			if (handler.handleRequest(vaadinSession, request, response)) {
				return;
			}
		}

		// Request not handled by any RequestHandler
		response.sendError(HttpStatusCode.NOT_FOUND.getCode(),
				"Request was not handled by any registered handler.");

	} catch (final SessionExpiredException e) {
		handleSessionExpired(request, response);  // <--- HERE
	} catch (final Exception e) {
		handleExceptionDuringRequest(request, response, vaadinSession, e);
	} finally {
		requestEnd(request, response, vaadinSession);
	}
}

On browser console:
Failed to load translations for language: fi Error: Failed fetching translations.
at DefaultBackend.loadTranslations (backend.ts:21:13)
at async I18n.updateLanguage (index.ts:219:28)
at async I18n.configure (index.ts:113:5)

My setup:

@layout.tsx:

export default function MainLayout() {
...
  useEffect(() => {
      i18n.configure();
  }, [])
...
}

SecurityConfig.java:

@EnableWebSecurity(debug = false)
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
public class SecurityConfig {
  @Value("${jwt.secret}")
  private String authSecret;    

  @Value("${jwt.expires}")
  private Long expiresIn;    

  private final RouteUtil routeUtil;

  public SecurityConfig(RouteUtil routeUtil) {
    this.routeUtil = routeUtil;
  }

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // Configure authorization rules
    PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults();
    http.authorizeHttpRequests(registry -> registry
        .requestMatchers(routeUtil::isRouteAllowed).permitAll() // Hilla internal requests
        .requestMatchers(mvc.matcher("/.well-known/**")).permitAll()
    );

    // Configure OAuth2
    http.with(VaadinSecurityConfigurer.vaadin(), configurer -> configurer
      .oauth2LoginPage("/oauth2/authorization/keycloak")
    );

    // Configure session management for stateless
    http.sessionManagement(sessionManagement -> sessionManagement
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    );

    http.with(new VaadinStatelessSecurityConfigurer<>(), cfg -> cfg
      .withSecretKey().secretKey(
          new SecretKeySpec(Base64.getDecoder().decode(authSecret), JwsAlgorithms.HS256)).and()
      .issuer("com.example").expiresIn(expiresIn)
    );

    return http.build();
  }
}

I’m using Vaadin version 24.9.0 and Spring Boot 3.5.6.

Do I have something wrong with my setup or is there perhaps an issue with Vaadin/Hilla/i18n, or something else?