Couldn't find route for ... / com.vaadin.flow.router.NotFoundException

I have a working custom login page (including a customised okta login widget) made with Vaadin version 14 based on samples like:
okta java spring custom login sample
vaadin oauth2 tutorial

Its working fine with v14 :

@Route(value = "custom-login")
@AnonymousAllowed
@PageTitle("Login")
@JavaScript("https://global.oktacdn.com/okta-signin-widget/5.16.1/js/okta-sign-in.min.js")
@StyleSheet("https://global.oktacdn.com/okta-signin-widget/5.16.1/css/okta-sign-in.min.css")
public class LoginView extends VerticalLayout implements AfterNavigationObserver, BeforeEnterObserver {
  public void afterNavigation(AfterNavigationEvent event) {
    QueryParameters queryParameters = event.getLocation().getQueryParameters();
    String reqParamState = queryParameters.getSingleParameter("state").orElse("");
    // if we don't have the state parameter redirect
    if (reqParamState == null || reqParamState.isEmpty()) {
           UI.getCurrent().navigate(oktaOauth2RedirectUri);

application.properties :
okta.oauth2.redirect-uri=/authorization-code/callback

After the user authenticates, they are redirected back to the application and a local cookie session is created.

But with Vaadin version 24 I got:

http://127.0.0.1:8081/authorization-code/callback
Could not navigate to 'authorization-code/callback'
Available routes:
* [<root>]
...
DEBUG com.vaadin.flow.router.AbstractRouteNotFoundError:65 - Route is not found
com.vaadin.flow.router.NotFoundException: **Couldn't find route for 'authorization-code/callback'**
	at com.vaadin.flow.component.UI.handleErrorNavigation(UI.java:2113)
	at com.vaadin.flow.component.UI.renderViewForRoute(UI.java:2022)
	at com.vaadin.flow.component.UI.browserNavigate(UI.java:1877)
	at com.vaadin.flow.component.ComponentEventBus.fireEventForListener(ComponentEventBus.java:239)
	at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:488)
	at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$dd1b7957$1(ComponentEventBus.java:298)
	at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:475)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:475)
	at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:62)
	at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:73)
	at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:550)
	at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$6(ServerRpcHandler.java:531)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:531)
	at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:358)
	at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:137)
	at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:63)
	at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1664)
	at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:398)
	at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:106)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:633)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:409)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:304)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:268)
	at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:142)
	at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:178)
	at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:51)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:219)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
	at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
	at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:243)
	at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
	at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
	at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:238)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)

Is it possible somehow to tell vaadin not to touch this url?

I tried also with the following code but got same error:

            Anchor redirectLink = new Anchor(oktaOauth2RedirectUri, "(redirect)");
            //redirectLink.getElement().setAttribute("router-ignore", true);
            redirectLink.setRouterIgnore(true);
            add(redirectLink);
            //UI.getCurrent().navigate(oktaOauth2RedirectUri);
            UI.getCurrent().getPage().setLocation(oktaOauth2RedirectUri);

If you move the logic in then BeforeEnterObserver method, you can use the event forwardToUrl() method.
But UI.getCurrent().getPage().setLocation(oktaOauth2RedirectUri); should also work

image
Even with beforeEnterEvent.forwardToUrl(oktaOauth2RedirectUri); I got the above could not navigate to error. The url was changed from /custom-login to /authorization-code/callback but I suppose vaadin wants to render a view for this instead of leave it to the spring security oauth2 client okta login layer to handle it.

What happens if you access that URL directly from another tab? Does this redirect you properly?

If no: Springā€™s security chain is not really in play - because if it wouldā€¦ it would have been called because it sits in front of Vaadin

2 Likes

From another tab it gets the same.
When opening the protected vaadin views there are forwarded to the LoginView at the url /custom-login fine then the LoginView forwards to /authorization-code/callback because there are no url parameters set for okta yet. But vaadin tries to find a view with @Route(ā€œ/authorization-code/callbackā€) but it should not because this belongs to the okta oauth client/login layer/handler/security filter(?).
Everything was fine with vaadin v14, but v24 gives this route not found exception.
Maybe my configuration is wrong?

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {

    private static final String LOGIN_URL = "/custom-login";
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        http.oauth2Login(loginConf -> {
            loginConf.loginPage(LOGIN_URL).permitAll();
            loginConf.redirectionEndpoint(redirectConf ->
                            redirectConf.baseUri("/authorization-code/callback*")
            );
        });
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        // Customize your WebSecurity configuration.
        // @formatter:off
        web.ignoring()
                .requestMatchers(regexMatcher("\\/\\?v-r=uidl&v-uiId=\\d+")) // example: /?v-r=uidl&v-uiId=58
                .requestMatchers(
                        "/authorization-code/callback**",
                        "/js/**", "/webjars/**",
                        // Vaadin Flow static resources
                        "/VAADIN/**",
...
       // @formatter:on
        super.configure(web);
    }

Your configuration looks outdated. Compare it with an example from start.vaadin.com


I do not see any option here to include security.

Start a project ā†’ then you can see way more options ā†’ once you add a protected view security is applied

1 Like

Thanks, first time I did not click the Start project button before Download.
I did compare the SecurityConfiguration classes.
I am using the okta spring dependency, I think there should be something wrong in that therefore it does not catch the callback url and then goes to the vaadin router. Possibly something changed with the spring security version upgrade.

<dependency>
            <groupId>com.okta.spring</groupId>
            <artifactId>okta-spring-boot-starter</artifactId>
            <version>3.0.7</version>
        </dependency>

At least the redirect URI looks a bit weird to me with the /callback* part.
The Spring Security documentation example for custom redirect URI uses /login/oauth2/callback/*, so a / before the asterisk.

I would suggest you to go through the Spring documentation and double-check your OAuht2 configuration

https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html
https://docs.spring.io/spring-security/reference/servlet/oauth2/login/advanced.html#oauth2login-advanced-redirection-endpoint

1 Like

I took this sample project to start, no Vaadin included, its working fine:
https://github.com/okta/samples-java-spring/tree/master/custom-login

   @Configuration
    static class OAuth2SecurityConfigurerAdapter {

        @Bean
        SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

            http.authorizeHttpRequests((requests) -> requests
                            .requestMatchers("/", "/custom-login", "/css/**").permitAll()
                            .anyRequest().authenticated()
                    )
                    .exceptionHandling().accessDeniedHandler((req, res, e) -> res.sendRedirect("/403"))
                    .and()
                    .logout().logoutSuccessUrl("/")
                    .and()
                    .oauth2Client()
                    .and()
                    .oauth2Login().redirectionEndpoint().baseUri("/authorization-code/callback*");

            return http.build();
        }
    }

This is the configuration in my old vaadin version14 project, also working fine:

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    /**
     * Require login to access internal pages and configure login form.
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // Vaadin handles CSRF internally
        http.csrf().disable()
                // Register our CustomRequestCache, which saves unauthorized access attempts, so the user is redirected after login.
                .requestCache().requestCache(new CustomRequestCache())

                // Restrict access to our application.
                .and().authorizeRequests()
                // Allow all Vaadin internal requests.
                .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
                .antMatchers("/login**", "/custom-login**").permitAll()
                // Allow all requests by logged-in users.
                .anyRequest().authenticated()

                // Configure the login page.
                .and().oauth2Client()
                .and().oauth2Login().redirectionEndpoint()
                .baseUri("/authorization-code/callback*")
        ;
    }

This is the configuration in my upgraded vaadin version24 project, this throws route not found exception:

@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {
    private static final String LOGIN_URL = "/custom-login";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        http.oauth2Client(Customizer.withDefaults());
        http.oauth2Login(loginConf -> {
//            loginConf.loginPage(LOGIN_URL).permitAll();
            loginConf.redirectionEndpoint(redirectConf ->
                            redirectConf.baseUri("/authorization-code/callback*")
            );
        });
        setOAuth2LoginPage(http, LOGIN_URL);
    }

application.properties are the same:

spring.security.oauth2.client.provider.okta.authorization-uri=http://localhost:${server.port}/custom-login
spring.security.oauth2.client.provider.okta.user-name-attribute=email

okta.oauth2.issuer=https://....com/oauth2/default
okta.oauth2.client-id=...
okta.oauth2.client-secret=...
okta.oauth2.redirect-uri=/authorization-code/callback
okta.oauth2.scopes=openid,email,profile

Probably it will not solve, but if you are using Spring Boot 3.4.3, can you try to downgrade to 3.4.2? Thereā€™s a known issue with configuration classes that affects Vaadin spring.

Maybe nothing changes, but at least we can discard this option.

BTW, which exact Vaadin version are you using?

Vaadin 24.6.6
Same com.vaadin.flow.router.NotFoundException: Couldnā€™t find route for ā€˜authorization-code/callbackā€™ with:

<java.version>17</java.version>
<vaadin.version>24.6.6</vaadin.version>
<okta.spring.version>3.0.7</okta.spring.version>
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!-- <version>3.4.3</version> -->
        <version>3.4.2</version>
</parent>
        <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-bom</artifactId>
                <version>${vaadin.version}</version>
                <type>pom</type>
                <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.okta.spring</groupId>
            <artifactId>okta-spring-boot-starter</artifactId>
            <version>3.0.7</version>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <!-- Replace artifactId with vaadin-core to use only free components -->
            <!-- <artifactId>vaadin</artifactId> -->
            <artifactId>vaadin-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-spring-boot-starter</artifactId>
        </dependency>
       <dependency>
            <groupId>com.okta.spring</groupId>
            <artifactId>okta-spring-sdk</artifactId>
            <version>${okta.spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

Can you publish an example project on GitHub? Or attach a zip?

Also setting logging.level.org.springframework.security=TRACE could help to understand why Spring Security is not handling the URL.

I would also suggest you to try to use the configuration suggested in the Spring documentation instead of using custom values: Core Configuration :: Spring Security

example project

I had a quick look at the project.
The issue seems to be in the LoginView: you are using UI.navigate() in afterNavigation but that method is used for Vaadin internal navigation, so the Spring Security filter is not intercepting it because the request is a POST on / path.

I commented it and uncommented beforeEnterEvent.forwardToUrl(oktaOauth2RedirectUri); in the beforeEnter method. Doing so, the OAuth2LoginAuthenticationFilter handles the request.

However, I think your redirect to /authorization-code/callback in the login view is wrong. You should redirect to the Okta entry point instead; then the identity provider will redirect the browser to /authorization-code/callback to complete the authentication.

The example project without Vaadin you linked, seems to start the login process from the client side using some Okta Javascript API, and the URL computed in the login controller is passed to that Javascript; there is no server-side redirect on login endpoint, only the rendering of the login page.

Unfortunately, I have no time to go deeper currently.

1 Like