Docs

Documentation versions (currently viewingVaadin 24)

Protect Views

In this guide, you’ll learn how to control access to specific views both declaratively and programmatically. A hands-on mini-tutorial at the end will help you apply these concepts in a real Vaadin application.

Declarative View Security

The easiest way to grant or deny access to a Flow view is to use annotations. The following annotations are supported:

  • @AnonymousAllowed allows access to unauthenticated users.

  • @PermitAll allows any authenticated user to navigate to the view.

  • @RolesAllowed allows users having the roles specified in the annotation value to navigate to the view.

  • @DenyAll prevents everyone from navigating to the view. By default, all Flow views are inaccessible unless explicitly annotated.

Note
The @AnonymousAllowed annotation is a Vaadin-specific annotation; the others are Jakarta annotations (JSR-250). The Spring Security annotations @Secured and @PreAuthorize are not supported on views.

The following example uses @AnonymousAllowed to allow all users — both authenticated and unauthenticated — to access the view:

@Route("public")
@PageTitle("Public View")
@AnonymousAllowed
public class PublicView extends Main {
    ...
}

In business applications that require authentication for everything, you typically have to do this on the login view.

The next example uses @PermitAll to allow only authenticated users — with any role — to access the view:

@Route("private")
@PageTitle("Private View")
@PermitAll
public class PrivateView extends Main {
    ...
}

This example uses @RolesAllowed to allow users with the ADMIN role to navigate to the view:

@Route("admin")
@PageTitle("Admin View")
@RolesAllowed("ADMIN")
public class AdminView extends Main {
    ...
}
Important
Router layouts are also protected
When protecting views, ensure the router layout also allows access. If a view is accessible but its parent layout is restricted, users will still be blocked.

Annotation Inheritance

Security annotations are inherited from the closest superclass that has them. Annotating a subclass overrides any inherited annotations. Interfaces aren’t checked for annotations, only classes.

In the following example, UserListingView requires the ADMIN role:

@RolesAllowed("ADMIN")
public abstract class AbstractAdminView extends VerticalLayout {
    ...
}

@Route(value = "user-listing")
public class UserListingView extends AbstractAdminView {
    ...
}

While multiple security annotations can be applied to a single view, doing so can cause conflicts and is not recommended. However, if multiple annotations exist on a view, they override each other in this order:

  • @DenyAll overrides all other annotations;

  • @AnonymousAllowed overrides @RolesAllowed and @PermitAll; and

  • @RolesAllowed overrides @PermitAll.

Programmatic View Security

Vaadin provides API:s for protecting views programmatically. It’s more verbose than using the annotations, but gives you greater control.

Making a Custom Navigation Access Checker

If you need more fine-grained control over how access is granted to views, you can implement a custom NavigationAccessChecker:

@Component 1
class CustomAccessChecker implements NavigationAccessChecker {

    @Override
    public AccessCheckResult check(NavigationContext context) {
        // Check whether to allow or deny access
    }
}
  1. Registers the navigation access checker as a singleton Spring bean.

The NavigationContext object contains information about where you’re trying to navigate, such as the view class, route parameters, and query parameters. It also contains the principal of the current user.

Since the access checker is a Spring bean, you inject other beans into it. For example, you may want to lookup additional information in order to make the access decision.

Once you’ve made a decision, you have to return an AccessCheckResult. The AccessCheckResult determines whether navigation is allowed, denied, or deferred. There are four possible outcomes:

AccessCheckResult.allow()

Access is granted.

AccessCheckResult.neutral()

The access checker cannot make a decision based on the given navigation information. Another access checker have to make the decision, or access will be denied.

AccessCheckResult.deny()

Access is denied.

AccessCheckResult.reject()

Access is denied because of a misconfiguration or critical development time error.

Note
The security annotations are actually enforced by a built-in access checker.

Enabling a Navigation Access Checker

To enable a custom NavigationAccessChecker, create a new NavigationAccessControlConfigurer Spring bean:

@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {

    @Bean
    static NavigationAccessControlConfigurer navigationAccessControlConfigurer( 1
            CustomAccessChecker customAccessChecker) {
        return new NavigationAccessControlConfigurer()
                .withNavigationAccessChecker(customAccessChecker); 2
    }
    ...
}
  1. The @Bean method must be static to prevent bootstrap errors caused by circular dependencies in bean definitions.

  2. CustomAccessChecker is now the only enabled access checker.

You can have multiple access checkers active at the same time. When you navigate to a view, they will all be consulted.

Note
To enable the built-in annotated view access checker, call NavigationAccessControlConfigurer.withAnnotatedViewAccessChecker().

An AccessCheckDecisionResolver computes the final access decision based on the results of every access checker. The default implementation makes the decision by applying the following 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

Important
The built-in annotated view access checker never returns NEUTRAL. It either grants or denies access.

By default, having access checkers both allowing and denying access at the same time is considered a configuration error. This can happen if you combine the built-in annotated view access checker with a custom access checker. If this is what you want, you have to create a custom AccessCheckDecisionResolver. This, and more, is covered in the Navigation Access Control Reference Guide.

Controlling Access within Views

Sometimes, you want multiple roles to be able to access a view, but limit what they can do within it. For instance, one role may have full read-write access whereas another role has only read-only access. To check the roles of the current user, inject an AuthenticationContext object into your view:

@Route("")
@PermitAll 1
public class MyView extends Main {

    public MyView(AuthenticationContext authenticationContext) {
        if (authenticationContext.hasRole("ADMIN")) { 2
            // Set up the UI for an admin
        } else {
            // Set up the UI for normal users
        }
    }
    ...
}
  1. All authenticated user have access to the view.

  2. Administrators can do more inside the view than normal users.

AuthenticationContext has multiple methods for checking the roles and authorities of the current user. Refer to the Javadoc for more information.

Role Constants

To reduce the risk of typos, consider defining all your application’s roles as constants. In the security package, make a Roles class:

public final class Roles {
    public static final String ADMIN = "ADMIN";
    public static final String USER = "USER";

    private Roles() {}
}

Then refer to the constants in annotations and method calls:

@Route
@RolesAllowed(Roles.ADMIN)
public class AdminView extends Main {
    ...
}
@Route("")
@PermitAll
public class MyView extends Main {

    public MyView(AuthenticationContext authenticationContext) {
        if (authenticationContext.hasRole(Roles.ADMIN)) {
            // Set up the UI for an admin
        } else {
            // Set up the UI for normal users
        }
    }
    ...
}

Try It

In this mini-tutorial, you’ll learn how to add declarative view security to a real Vaadin application. You’ll also learn how to control access programmatically inside a view. The tutorial uses the project from the Add Logout guide. If you haven’t completed that tutorial yet, do it now before proceeding.

Create Role Constants

Create a new class Roles in the [application package].security package:

public final class Roles {
    public static final String ADMIN = "ADMIN";
    public static final String USER = "USER";

    private Roles() {
    }
}

Then update the userDetailsManager() method of the SecurityConfig class to use the new constants:

@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
    ...

    @Bean
    public UserDetailsManager userDetailsManager() {
        LoggerFactory.getLogger(SecurityConfig.class)
            .warn("Using in-memory user details manager!");
        var user = User.withUsername("user")
                .password("{noop}user")
                .roles(Roles.USER)
                .build();
        var admin = User.withUsername("admin")
                .password("{noop}admin")
                .roles(Roles.ADMIN)
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
Create Admin View

Create a new class AdminView in the [application package].todo.ui.view package:

import com.example.application.security.Roles; 1

import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;

@Route
@PageTitle("Task Admin")
@Menu(order = 10, icon = "vaadin:wrench", title = "Task Admin")
@RolesAllowed(Roles.ADMIN)
public class AdminView extends Main {

    public AdminView() {
        setText("Admin View");
    }
}
  1. Replace with real package.

Now navigate to: http://localhost:8080

Log in as an ADMIN. You should see Task Admin in the navigation menu. Clicking it should take you to the admin view.

Now log out and log back in as a USER. The Task Admin menu item should no longer be visible.

Attempt to access http://localhost:8080/admin directly. You should see an access denied error.

Make the Task List Read-Only For Users

So far all authenticated users have been able to add tasks to TodoView. You’ll now change it so that only users with the ADMIN role can add tasks. Open TodoView and change the constructor as follows:

@Route("")
@PageTitle("Task List")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List")
@PermitAll
public class TodoView extends Main {

    public TodoView(TodoService todoService, Clock clock,
            AuthenticationContext authenticationContext) {

        // The rest of the constructor omitted

        if (authenticationContext.hasRole(Roles.ADMIN)) {
            add(new ViewToolbar("Task List",
                ViewToolbar.group(description, dueDate, createBtn))); 1
        } else {
            add(new ViewToolbar("Task List")); 2
        }
        add(todoGrid);
    }
    ...
}
  1. Only create the toolbar if the user is an ADMIN.

Go back to your browser and try the application. The toolbar should only be visible in the Task List when you are logged in as ADMIN.

Note
The description, dueDate, and createBtn components are created for all users, even though they are only used by administrators. Technically this is a waste of memory, but this time it is acceptable since the view is so small.
Final Thoughts

Now that your views are secure, the next step is to protect application services. Learn how in the Protect Services guide.