Security Best Practices

Authentication and Authorization

Vaadin lets you choose which authentication and authorization framework you want to use, instead of bundling any specific one. Vaadin is fully compatible with the most used security solutions in the Java ecosystem, including but not limited to Spring Security, JAAS and Apache Shiro. The Vaadin-Spring addon has helpers for developers to integrate into the security mechanisms of those respective frameworks.

Since Vaadin is a server-side framework, credential processing always happens on the server, away from any possible attack surface. Credentials are never transmitted to the client unless explicitly done so by the developer.

Generally, it is recommended that the developer double-checks user identity and access rights for each call from the client. This can be automated with, for example, Spring Security and view-based authentication using roles. What typically can’t be automated by these frameworks is data-based access rights, such as limiting access to specific entities.

As an example, if the server receives an ID of a User object to be displayed in, for example, a URL request parameter ({yourapp.com}/users/4/edit), then the ID in question can be freely changed by an attacker. The application needs to be aware of this and check if the currently logged-in user has access rights to this entity. This is something that is common to all UI frameworks, and not specific to Vaadin.

Examples for integrating Spring Security can be found in the Spring integration section.

View-Based Access Control

Since Vaadin 21, it is possible to use a built-in view-based access control mechanism to secure Flow navigation targets. However, as mentioned in Authentication and Authorization, it is not mandatory to use it, so it is not enabled by default. The view-based access control mechanism uses the @AnonymousAllowed, @PermitAll, @RolesAllowed, and @DenyAll annotations on view classes to define the access control rules. By default any view without an annotation is secured - it acts like it would have the @DenyAll annotation in it.

See View-Based Access Control for detailed information about enabling the access control mechanism in Vaadin Flow Spring Boot applications.

To enable and use this mechanism, the following should be added to a Vaadin Flow application (if it does not exist already):

  • A security realm.

  • A login view.

  • Logout capability.

  • A VaadinServiceInitListener that adds ViewAccessChecker as the BeforeEnterListener of all UIs.

  • A Service Provider under META-INF/services to load above the custom ServiceInitListener via the Java SPI loading mechanism.

Configuring a Security Realm

A security realm is a mechanism for storing and mapping the users with their passwords and roles. Configuring a security realm depends on the application server you are using to run Vaadin application. For example, if you use Apache Tomcat or TomEE, follow Realm Configuration How-To.

Handle User’s Login and Logout

You can handle login and logout in many ways, as well as how to get the authenticated user for a request. The View-based access control implementation in Vaadin uses getUserPrincipal() and isUserInRole(String) in HttpServletRequest to check whether the currently authenticated user has access to the view. Therefore, you need a way to put the user information into the request.

The example below uses the currently active Vaadin servlet request. It shows how to:

  • Check whether the user is currently authenticated to the application or not. The request is not available in background threads, so the isAuthenticated() method shows authentication state only in Vaadin request processing threads, otherwise it would always return false.

  • Authenticate the user with the given credentials. The authenticate() method fails if is called from a background thread.

  • Logout the user by invalidating the HTTP session and redirecting to the home page.

public class SecurityUtils {

    private static final String LOGOUT_SUCCESS_URL = "/";

    public static boolean isAuthenticated() {
        VaadinServletRequest request = VaadinServletRequest.getCurrent();
        return request != null && request.getUserPrincipal() != null;
    }

    public static boolean authenticate(String username, String password) {
        VaadinServletRequest request = VaadinServletRequest.getCurrent();
        if (request == null) {
            // This is in a background thread and we cannot access the request to
            // log in the user
            return false;
        }
        try {
            request.login(username, password);
            return true;
        } catch (ServletException e) {
            // login exception handle code omitted
            return false;
        }
    }

    public static void logout() {
        VaadinSession.getCurrent().getSession().invalidate();
        UI.getCurrent().getPage().setLocation(LOGOUT_SUCCESS_URL);
    }
}

Adding Login View

Having a login view is one of the basic requirements of many authentication and authorization mechanisms. It serves to redirect anonymous users to that page before granting access to view any protected resources.

@Route("login")
@PageTitle("Login")
public class LoginView extends VerticalLayout implements BeforeEnterObserver,
        ComponentEventListener<AbstractLogin.LoginEvent> {

    private static final String LOGIN_SUCCESS_URL = "/";

    private LoginForm login = new LoginForm();

    public LoginView() {
        addClassName("login-view");
        setSizeFull();

        setJustifyContentMode(JustifyContentMode.CENTER);
        setAlignItems(Alignment.CENTER);

        login.addLoginListener(this);

        add(new H1("Test Application"), login);
    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        if (beforeEnterEvent.getLocation()
            .getQueryParameters()
            .getParameters()
            .containsKey("error")) {
            login.setError(true);
        }
    }

    @Override
    public void onComponentEvent(AbstractLogin.LoginEvent loginEvent) {
        boolean authenticated = SecurityUtils.authenticate(
                loginEvent.getUsername(), loginEvent.getPassword());
        if (authenticated) {
            UI.getCurrent().getPage().setLocation(LOGIN_SUCCESS_URL);
        } else {
            login.setError(true);
        }
    }
}

In this example, Vaadin’s Login Form component is used for the sake of brevity. However, feel free to implement your own login view, if you wish.

Logout Capability

You typically let the user to log out by using a logout button. The following example shows a basic implementation of a logout button shown on the header of the main layout:

public class MainLayout extends AppLayout {

    public MainLayout() {
        H1 logo = new H1("Vaadin CRM");
        logo.addClassName("logo");
        HorizontalLayout header;
        if (SecurityUtils.isAuthenticated()) {
            Button logout = new Button("Logout", click ->
                    SecurityUtils.logout());
            header = new HorizontalLayout(logo, logout);
        } else {
            header = new HorizontalLayout(logo);
        }

        // Other page components omitted.

        addToNavbar(header);
    }
}

Adding VaadinServiceInitListener

In order to restrict access to views, a BeforeEnterListener must be registered for the VaadinService, to initialize and enable the ViewAccessChecker:

import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.server.auth.ViewAccessChecker;
import org.vaadin.example.views.login.LoginView;

public class ViewAccessCheckerInitializer implements VaadinServiceInitListener {

    private ViewAccessChecker viewAccessChecker;

    public ViewAccessCheckerInitializer() {
        viewAccessChecker = new ViewAccessChecker(); 1
        viewAccessChecker.enable(); 2
        viewAccessChecker.setLoginView(LoginView.class); 3
    }

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

The above code contains some notable components of the view-based access control mechanism:

  1. ViewAccessChecker, which is at the core of this access control mechanism, is instantiated.

  2. ViewAccessChecker is enabled. Note that it is not enabled by default.

  3. LoginView class is set to the viewAccessChecker instance. Now it knows where to redirect unauthenticated users.

  4. The viewAccessChecker instance is set as the BeforeEnterListener in the overridden serviceInit method. Now it is ready to intercept attempts to enter all views.

However, the above class still needs to be loaded, so we recommend following the instructions in the next step.

Enable Loading of VaadinServiceInitListener

To enable the Java SPI loading mechanism to load the above ViewAccessCheckerInitializer as the VaadinServiceInitListener, do the following:

  • Under resources/META-INF/services directory, create a file named exactly:

    com.vaadin.flow.server.VaadinServiceInitListener
  • Put the fully qualified name of ViewAccessCheckerInitializer into this newly created file. For example, if the ViewAccessCheckerInitializer class is in the org.vaadin.example.security package, the following value should be in the file:

    org.vaadin.example.security.ViewAccessCheckerInitializer

This Service Provider config file triggers the Java SPI loading mechanism to load ViewAccessCheckerInitializer during application startup. More information on this can be found in VaadinServiceInitListener.

Access Annotations

Before we see some examples of access annotations, it is worth having a closer look at the annotations, and their meaning when applied to a view:

  • @AnonymousAllowed permits anyone to navigate to the view without any authentication or authorization.

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

  • @RolesAllowed grants access to users having the roles specified in the annotation value.

  • @DenyAll disallows everyone from navigating to the view. This is the default, which means that, if a view is not annotated at all, the @DenyAll logic is applied.

Some usage examples:

@Route(value = "", layout = MainView.class)
@PageTitle("Public View")
@AnonymousAllowed
public class PublicView extends VerticalLayout {
    // ...
}
@Route(value = "private", layout = MainView.class)
@PageTitle("Private View")
@PermitAll
public class PrivateView extends VerticalLayout {
    // ...
}
@Route(value = "admin", layout = MainView.class)
@PageTitle("Admin View")
@RolesAllowed("ROLE_ADMIN") // <- Should match one of the user's roles (case-sensitive)
public class AdminView extends VerticalLayout {
    // ...
}

Now, if the application is started by navigating to http://localhost:8080, PublicView contents should be available without any authentication. However, by navigating to http://localhost:8080/private or http://localhost:8080/admin, the user is redirected to the specified LoginView.

If the user is already authenticated and tries to navigate to the view they are not allowed to, an error message is displayed. The message depends on the application mode:

  • In development mode, Vaadin shows an Access denied message with the list of available routes.

  • In production mode, Vaadin shows the RouteNotFoundError view, which shows Could not navigate to 'RequestedRouteName' message by default. For security reasons, the message does not tell whether the navigation target exists.

The following example shows how the security annotations are inherited from the closest parent class that has them.

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

@Route(value = "user-listing", layout = MainView.class)
@PageTitle("User Listing")
public class UserListingView extends AbstractAdminView {
    // ...
}

Annotating a child class will override any inherited annotations. Interfaces are not checked for annotations, but only classes. By design, the annotations are not read from parent layouts or "parent views", as this would make things unnecessarily complex to determine which security level should be applied. If multiple annotations are specified on a single view class, the following rules are applied:

  • DenyAll overrides other annotations

  • AnonymousAllowed overrides RolesAllowed and PermitAll

  • RolesAllowed overrides PermitAll

However, we do not recommend specifying more than one of the above access annotations on a view class. It is confusing and probably has no logical purpose.

Application State

The server is always aware of your application’s state. This means that, like client-side applications, the server is aware of what is currently visible on the end user’s screen. Hence, Vaadin denies actions to components that are not currently visible on the screen, or those that have been disabled on the server.

For instance, if the developer sets a component to be disabled, this effect is set both on the server and the client. On the client, an attacker can circumvent this (attackers have full control over anything in the browser), but the server will block any attempt to interact with the component and a warning is printed to the server logs.

Button button = new Button("Click me for effect!");
button.setEnabled(false);
button.addClickListener(e -> {
    // If the Button is disabled, this listener will not run,
    // even if an attacker enables the button client side.
});

Data Validation

In a Vaadin application, the data binding API supports data validation on the server, which cannot be by-passed with client-side attacks. Vaadin components do support client-side validation to increase the responsiveness of the application, but the developer should be aware that these should be used purely for convenience, since they are easily circumvented in the browser.

As with other web applications, all data coming from the client should always be validated once it reaches the server. It is not safe to rely on only client-side validation. Vaadin provides a set of pre-created server side validators for this purpose. In addition, the developer is free to use any Java API for validating the data, including connecting to external services. Vaadin also has a built-in integration with Java’s Bean Validation (JSR 303) standard.

Data coming from a data store (such as a database) and inserted as HTML into DOM elements (for example, setting innerHTML for elements or using HTML mode in component captions) should also be escaped. Please see the chapter for XSS for more information.

Web Services

No public Web Services are necessary in Vaadin applications. All communication in Vaadin goes through a single HTTP request handler used for RPC requests using the standard Servlet Java API. With Vaadin, you never open up your business logic as web services and thus there are less attack entry points to your Vaadin application.

SSL and HTTPS

Vaadin always recommend developers to set up secure server endpoints and run all communication exclusively under HTTPS. Vaadin works out-of-the-box with HTTPS, and there is nothing for the developer to configure in your application code. Please refer to the documentation of your servlet container for details on how to set up HTTPS on your server.