Docs

Navigation Access Control

Explains navigation access control and how it can be customized.

Navigation access control decides, for every navigation, whether the current user may open the target view. It evaluates a set of pluggable rules — such as the access annotations — and then either renders the view, redirects the user to the log-in view, or shows an error view.

Architecture

The Navigation Access Control mechanism is composed of three fundamental components: NavigationAccessControl; NavigationAccessChecker; and AccessCheckDecisionResolver.

NavigationAccessControl is the entry point for navigation security. It listens for navigation events, and it evaluates all security rules. Based on the results, it allows or denies access to the target view.

NavigationAccessChecker interface implementers represent the security rules that are evaluated during a navigation event.

AccessCheckDecisionResolver has the responsibility of taking the final decision to allow or deny a navigation, based on the result of the evaluation of the security rules.

Navigation Access Control Architecture

NavigationAccessControl can be configured to evaluate multiple NavigationAccessChecker. For example, you can enable annotation-based view access check and path-based access check. During a navigation request, both checkers are evaluated against the current NavigationContext and they produce an AccessCheckResult.

The AccessCheckDecisionResolver analyzes the results and provides the final decision. The access control uses that decision to proceed with the navigation or reroute to the login view or to an error page.

Access Check Results

Each navigation access checker returns an AccessCheckResult, which holds the decision taken by the checker — and potentially an informative reason about the decision. The decision can be ALLOW, DENY, NEUTRAL, or REJECT. ALLOW and DENY are self-explanatory. NEUTRAL means that the checker doesn’t have enough information to make a decision, and it delegates the responsibility to the other configured checkers. REJECT has the same meaning of DENY, but it’s used to signal a critical situation where the checker cannot make the decision because of a system configuration error.

In development mode, a rejection is handled by throwing an exception so that the situation can be detected immediately. The AccessCheckResult object is created by calling its factory methods allow, deny, neutral, or reject. Alternatively, the same methods can be called on NavigationContext instance.

Built-in Access Checkers

Flow provides two implementations of the NavigationAccessChecker interface.

AnnotatedViewAccessChecker

AnnotatedViewAccessChecker looks for an access annotation (@AnonymousAllowed, @PermitAll, @RolesAllowed, or @DenyAll) on the target view class. It evaluates them against the current navigation context. AnnotatedViewAccessChecker delegates the check to an instance of AccessAnnotationChecker, that can be extended to provide custom rules.

RoutePathAccessChecker

RoutePathAccessChecker evaluates the path used to navigate to a view. It requires an implementation of AccessPathChecker, that is responsible for the effective path verification. Vaadin’s Spring integration provides an implementation that evaluates the path against the Spring Security configuration, as described in URL-Pattern-Based Security. For other security frameworks, provide your own AccessPathChecker implementation.

Decision Resolver

The AccessCheckDecisionResolver component is responsible for analyzing the results provided by the navigation access checkers, and for computing the final decision to grant or deny access to a view.

The default implementation makes the decision by applying the following rules:

Table 1. Default Decision Resolver Rules
Navigation Access Checkers Results Decision

ALL ALLOW

ALLOW

ALLOW + NEUTRAL

ALLOW

ALL DENY

DENY

DENY + NEUTRAL

DENY

ALL NEUTRAL

DENY

ALLOW + DENY

REJECT

ALLOW + DENY + NEUTRAL

REJECT

As shown in the above table, if the navigation access checkers do not agree on the decision — excluding neutral votes — the default resolver rejects the navigation, causing an exception to be thrown in development mode. This situation happens usually because of invalid security configurations. For example, a view may be annotated with @AnonymousAllowed, but the Spring Security configuration has a request matcher for the view path that grants access only to an authenticated user.

In error handling phase, almost the same rule applies with the exception that if all the results are NEUTRAL, the access is granted. The reason is that, in this case, the target of the navigation is supposed to be an error handler component that do not expose sensible information.

Table 2. Default Decision Resolver Rules For Error Handling Phase
Navigation Access Checkers Results Decision

ALL ALLOW

ALLOW

ALLOW + NEUTRAL

ALLOW

ALL NEUTRAL

ALLOW

ALL DENY

DENY

DENY + NEUTRAL

DENY

ALLOW + DENY

REJECT

ALLOW + DENY + NEUTRAL

REJECT

Configuration

In a Spring application, VaadinSecurityConfigurer enables navigation access control automatically. In a plain servlet application, a VaadinServiceInitListener enables it, as described in Setting Up Security. The following sections show how to customize it.

Spring Applications

When using VaadinSecurityConfigurer for configuring Spring Security, the Navigation Access Control feature is enabled by default. You can disable it by calling the enableNavigationAccessControl(false) method on the configurer. By default, only the AnnotatedViewAccessChecker is active.

For a fine-grained configuration, you can expose a bean of type NavigationAccessControlConfigurer. NavigationAccessControlConfigurer allows activating out-of-the-box navigation access checkers or adding new ones, providing a custom decision resolver, completely disable the functionality, etc.

Note
If VaadinSecurityConfigurer is not used, exposing the NavigationAccessControlConfigurer bean is mandatory to activate navigation access control.

In the following example, Navigation Access Control is configured to activate route path checker, a custom checker instance and all available NavigationAccessChecker beans that extend VotingNavigationAccessChecker. It also provides a custom decision resolver.

Source code
Customize NavigationAccessControl in Spring Project
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurerCustomizer() {
    return new NavigationAccessControlConfigurer()
            .withRoutePathAccessChecker() 1
            .withNavigationAccessChecker(new CustomChecker()) 2
            .withAvailableNavigationAccessCheckers(checker ->
                checker instanceof VotingNavigationAccessChecker  3
            )
            .withDecisionResolver(new CustomDecisionResolver()); 4
}
  1. Activates the route path access checker. The implementation is provided by the Vaadin Spring module and exposed as a bean.

  2. Adds a custom navigation access checker, by creating a new instance.

  3. Activates all registered navigation access checker bean, that matches the predicate.

  4. Sets a custom decision resolver.

Important
When using VaadinSecurityConfigurer in the same configuration class where you define the NavigationAccessControlConfigurer bean, make sure to declare the bean method as static to prevent bootstrap errors due to circular dependencies in bean definitions.
Source code
Define NavigationAccessControlConfigurer Bean with VaadinSecurityConfigurer
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    static NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
        return new NavigationAccessControlConfigurer()
            .withRoutePathAccessChecker()
            .withDecisionResolver(new CustomDecisionResolver());
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            // VaadinSecurityConfigurer will use the NavigationAccessControlConfigurer bean
        }).build();
    }
}
Define NavigationAccessControlConfigurer Bean with VaadinSecurityConfigurer

Consult the NavigationAccessControlConfigurer Javadoc for more information on how navigation access control can be customized.

URL-Pattern-Based Security

With RoutePathAccessChecker, a Flow route is protected by Spring Security request matchers for its path, in the same way as any other URL. The path used to navigate to the view is evaluated with Spring’s WebInvocationPrivilegeEvaluator:

Source code
Java
@Route("admin")
public class AdminView extends Div { }

@Route("protected/profile")
public class ProfileView extends Div { }

@Route("special/preview-feature")
public class PreviewFeatureView extends Div { }

@Route("")
public class HomeView extends Div { }

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/admin").hasRole("ADMIN")
                .requestMatchers("/protected/**").authenticated()
                .requestMatchers("/special/**")
                    .access((authentication, object) -> {
                        // Custom authorization logic
                        return new AuthorizationDecision(isPremiumUser(authentication));
                    })
                .requestMatchers("/")
                .permitAll()
        );

        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            // Configure Vaadin security settings
        });

        return http.build();
    }
}
Java

URL-based security can be enabled by activating the RoutePathAccessChecker component provided by Flow. To activate it, you need to define a NavigationAccessControlConfigurer bean in your Spring configuration, creating a new instance of that class and calling the withRoutePathAccessChecker() method.

Source code
Activate Both Annotated Views and URL-Pattern-Based Security
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
    return new NavigationAccessControlConfigurer()
            .withAnnotatedViewAccessChecker() 1
            .withRoutePathAccessChecker();    2
}
  1. Activation of annotation base view access checker.

  2. Activation of Spring URL-pattern-based checker.

You can also activate only the RoutePathAccessChecker, if you prefer to centralize the authorization configuration by only using the Spring Security URL-pattern-based security feature.

Source code
Activate Only URL-Pattern-Based Security
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
    return new NavigationAccessControlConfigurer()
            .withRoutePathAccessChecker();
}

When both checkers are active, the access annotations and the request matchers for the same view must agree. If one of them allows the navigation and the other denies it, the default decision resolver rejects the navigation, as described in Decision Resolver. To keep the rules for a view in one place, use only one of the two mechanisms for it.

Plain Servlet Applications

In a plain servlet application, pass the navigation access checkers and the decision resolver to the NavigationAccessControl constructor. Flow doesn’t provide an AccessPathChecker for plain servlet applications, but you can implement one that integrates with the security framework that you use. The following example is the plain servlet equivalent of the Spring example in Spring Applications:

Source code
Custom NavigationAccessControl in Plain Java Project
public class FooBarSecurityAccessPathChecker implements AccessPathChecker {
    @Override
    public boolean hasAccess(
        String path, Principal principal, Predicate<String> roleChecker) {
        // implementation omitted
    }
}

public class NavigationAccessCheckerInitializer implements VaadinServiceInitListener {

    private final NavigationAccessControl accessControl;

    public NavigationAccessCheckerInitializer() {
        accessControl = new NavigationAccessControl(List.of(
            new RoutePathAccessChecker(new FooBarSecurityAccessPathChecker()),
            new CustomChecker(),
            new VotingNavigationAccessChecker()
        ), new CustomDecisionResolver());
        accessControl.setLoginView(LoginView.class);
    }

    @Override
    public void serviceInit(ServiceInitEvent serviceInitEvent) {
        serviceInitEvent.getSource().addUIInitListener(uiInitEvent ->
            uiInitEvent.getUI().addBeforeEnterListener(accessControl));
    }
}

Customization

Custom Navigation Access Checker

If the built-in checkers aren’t enough, a custom NavigationAccessChecker can be implemented. NavigationAccessChecker has a single method that takes a NavigationContext as input and produces an AccessCheckResult as output.

The NavigationContext provides information about the current navigation, such as target view class and location, potential authenticated user and its roles.

For example, a navigation access check implementation that allows access to a voting view only when voting has been marked as open, could look like the following code:

Source code
VotingNavigationAccessChecker.java

When implementing a custom NavigationAccessChecker, it’s important to know that the checker is called also when the navigation is rerouted to an error handler view. The NavigationContext class provides the isErrorHandling() method to verify if the call happens during a navigation error handling phase. In this case, a custom checker would probably abstain from making a decision for the internal navigation, by returning a NEUTRAL result.

Source code
Neutral Result on Error Handling Phase
@Override
public AccessCheckResult check(NavigationContext context) {
    if (context.isErrorHandling()) {
        return AccessCheckResult.neutral();
    }
    ....
}

Custom Decision Resolver

If the default implementation doesn’t fit the requirements of the project, you can implement your own AccessCheckDecisionResolver. The interface defines a single method that takes as input the results computed by the navigation access checker and the current navigation context. Its output is an AccessCheckResult.

In the following example, the decision resolver allows access to the view if at least one of the checkers provided an ALLOW result:

Source code
CustomDecisionResolver.java

4164EB30-201D-4FD3-940F-03630A752AD5

Updated –