Docs

Service Access Control

The access annotations of browser-callable services, how Vaadin checks them, and how to audit the calls that it denies.

Vaadin checks every call to a browser-callable service against the access annotations of the service, before it reads the arguments of the call. Access is denied by default: nobody can call a method that has no access annotation, in a class that has none either.

Vaadin makes these checks itself, without Spring method security, and only for the calls that come from the browser. Java code that calls the service bean directly isn’t checked.

Access Annotations

These annotations control the access to a service:

@AnonymousAllowed

Allows everyone, including users who aren’t logged in. The annotation is in the package com.vaadin.flow.server.auth.

@PermitAll

Allows the users who are logged in.

@RolesAllowed

Allows the users who have at least one of the given roles.

@DenyAll

Denies everyone.

The last three are the Jakarta annotations in the package jakarta.annotation.security.

An annotation on the class applies to the public methods of the class that have no access annotation of their own. When a method has an access annotation, the annotations of the class don’t apply to it:

Source code
ProjectService.java

When a class or a method has more than one access annotation, the first of them in this list applies:

  1. @DenyAll

  2. @AnonymousAllowed

  3. @RolesAllowed

  4. @PermitAll

The access annotations of the service class also apply to the methods that the service inherits, as described in Inherited Methods.

Role Checks

Vaadin checks the roles of @RolesAllowed with the isUserInRole() method of the HTTP request. With Spring Security, this method adds the role prefix ROLE_ to a role name that doesn’t start with it. Both @RolesAllowed("ADMIN") and @RolesAllowed("ROLE_ADMIN") therefore match the granted authority ROLE_ADMIN. See Roles & Authorities.

Denied Calls

A denied call fails with the HTTP status 401 Unauthorized when nobody is logged in, and with 403 Forbidden when a user is. In the browser, the call rejects with an UnauthorizedResponseError or a ForbiddenResponseError, as described in Errors. In development mode, the error message lists the annotations that allow access.

With VaadinSecurityConfigurer, Spring Security already rejects a call to a method that doesn’t allow anonymous access when nobody is logged in. Vaadin’s check doesn’t run for such a call. The response is the same 401 Unauthorized.

The Current User

A service method can get the user who called it from AuthenticationContext, as described in Authentication Context. The methods of the class that return the user and the roles work in services. Its logout() method works only in Flow views.

Auditing Denied Calls

When Vaadin’s check denies a call, Vaadin publishes a Spring Security AuthorizationDeniedEvent, the same event that Spring method security publishes for a denied call. An application that audits denied access sees the denied service calls in the same audit trail.

Vaadin publishes the events only when the application has an AuthorizationEventPublisher bean. Spring Security uses the same bean to opt in to these events:

Source code
Java
@Configuration
public class AuditConfig {

    @Bean
    AuthorizationEventPublisher authorizationEventPublisher(
            ApplicationEventPublisher publisher) {
        return new SpringAuthorizationEventPublisher(publisher);
    }
}

The object of the event is an EndpointInvocation, which names the service and the method. It implements MethodInvocation, so a listener written for method security accepts it as it is:

Source code
Java
@Component
public class DeniedCallAuditor {

    @EventListener
    public void onAccessDenied(AuthorizationDeniedEvent<?> event) {
        if (event.getObject() instanceof EndpointInvocation invocation) {
            LoggerFactory.getLogger(getClass()).warn(
                    "Denied call: user={} service={} method={}",
                    event.getAuthentication().get().getName(),
                    invocation.getEndpointName(),
                    invocation.getMethodName());
        }
    }
}

The getThis() method returns the service bean, which is useful for routing an alert to the owner of the service. The getArguments() method always returns an empty array, and proceed() throws an exception. Vaadin checks the access before it reads the arguments, so the arguments of a denied call can’t end up in the audit log.

A call that Spring Security rejects before Vaadin’s check, as described in Denied Calls, doesn’t publish an EndpointInvocation. Spring Security publishes its own AuthorizationDeniedEvent for it, with the HTTP request as the object.

Cross-Site Request Forgery

Every call to a service carries a token that protects it from cross-site request forgery. A call without a valid token fails with 401 Unauthorized, even when the method allows anonymous access. See Cross-Site Request Forgery.

Updated –