> Markdown version of [Hilla](https://vaadin.com/docs/v24/building-apps/security/protect-services/hilla). Section index: [llms.txt](https://vaadin.com/docs/v24/building-apps/llms.txt)

# Protect Browser-Callable Services

In a Vaadin application using Hilla views, securing `@BrowserCallable` services is crucial to prevent unauthorized access. In this guide, you’ll learn how to control access to specific browser-callable services. A hands-on mini-tutorial at the end will help you apply these concepts in a real Vaadin application.

> **Caution: Browser-callable services don’t use Spring method security**
>
> Vaadin handles the protection of browser-callable services **outside Spring Security**. This means that if you inject a browser-callable service into another Java service or Flow view, it **will not be protected by default**.

## <a id="securing-the-services"></a>Securing the Services

Vaadin protects browser-callable services in the same way as it protects [Flow views](https://vaadin.com/docs/v24/building-apps/security/protect-views/flow.md) — with annotations.

> **Important:** All browser-callable services are **inaccessible by default** and require explicit annotations to grant access.

You can annotate both **service classes** and individual **service methods**. An annotation placed on the class applies to **all public methods** of the class. An annotation placed on a method **overrides any annotation on the class**.

The following annotations are supported:

- `@AnonymousAllowed` allows access to **unauthenticated** users.

- `@PermitAll` allows any **authenticated** user to call the service or method.

- `@RolesAllowed` allows users **having the roles** specified in the annotation value to call the service or method.

- `@DenyAll` prevents **everyone** from calling the service or method.

> **Note:** The `@AnonymousAllowed` annotation is a Vaadin-specific annotation; the others are Jakarta annotations (JSR-250).

The following example uses `@AnonymousAllowed` to allow **all users** — both authenticated and unauthenticated — to call the service:

```java
@BrowserCallable
// tag::snippet[]
@AnonymousAllowed
// end::snippet[]
public class PublicService {

    public void doSomething() {
        //...
    }
}
```

The following example combines `@PermitAll` and `@RolesAllowed` to allow **all authenticated users** to call the service, except for one method that can be called only by **administrators**:

```java
@BrowserCallable
// tag::snippet[]
@PermitAll
// end::snippet[]
public class ProtectedService {

    public void callableByAllUsers() { // (1)
    }

// tag::snippet[]
    @RolesAllowed(Roles.ADMIN) // (2)
// end::snippet[]
    public void callableByAdminsOnly() {
    }
}
```

1. Inherits its access permissions from the `@PermitAll` annotation on the class.

2. Overrides the `@PermitAll` annotation on the class, limiting the access.

You can also do the other way around. This example allows **only administrators** to call the service, except for one method that can be called by **all authenticated users**:

```java
@BrowserCallable
// tag::snippet[]
@RolesAllowed(Roles.ADMIN)
// end::snippet[]
public class ProtectedService {

// tag::snippet[]
    @PermitAll
// end::snippet[]
    public void callableByAllUsers() {
    }

    public void callableByAdminsOnly() {
    }
}
```

## <a id="using-spring-method-security-for-browser-callable-services"></a>Using Spring Method Security for Browser-Callable Services

[Flow services](https://vaadin.com/docs/v24/building-apps/security/protect-services/flow.md) are protected by Spring method security. If you have a **protected Flow service** that you want to **make available for Hilla views**, you can do that with some tweaking.

> **Note:** The following assumes you have already set up Spring method security. For details, see the [Protect Flow Services](https://vaadin.com/docs/v24/building-apps/security/protect-services/flow.md) guide.

By default, requests coming in from the browser are denied by Vaadin before the service is even called. This applies even when the service is proxied and protected by Spring Security.

To **bypass Vaadin’s security check and rely on Spring Security instead**, you have to add `@AnonymousAllowed` to the service, like this:

```java
@BrowserCallable
// tag::snippet[]
@AnonymousAllowed // (1)
@PreAuthorize("isAuthenticated()") // (2)
// end::snippet[]
public class ProtectedService {

    public MyData callableByAllUsers() {
    }

// tag::snippet[]
    @PreAuthorize("hasRole('" + Roles.ADMIN + "')")
// end::snippet[]
    public void callableByAdminsOnly(MyData data) {
    }
}
```

1. Bypass Vaadin’s security check.

2. Enable Spring method security instead.

> **Note:** Better support for Spring Security is planned for a future version of Vaadin Hilla. See [this ticket](https://github.com/vaadin/hilla/issues/3271) for more information.

> **Warning: Don’t configure Spring Security to use JSR-250 annotations**
>
> Spring method security has support for the JSR-250 annotations that Vaadin uses to protect browser-callable services. However, **Vaadin treats `@PermitAll` differently than Spring Security**. Whereas Vaadin allows access to *authenticated* users only, Spring Security allows access to *all* users.

## <a id="try-it"></a>Try It

In this mini-tutorial, you’ll learn how to secure browser-callable services in a real Vaadin application. The tutorial uses the project from the [Protect Views](https://vaadin.com/docs/v24/building-apps/security/protect-views/hilla.md#try-it) 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:

Roles.java

```java
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:

`SecurityConfig.java`

```java
@EnableWebSecurity
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {
    ...

    @Bean
    public UserDetailsManager userDetailsManager() {
        LoggerFactory.getLogger(SecurityConfig.class)
            .warn("Using in-memory user details manager!");
        var user = User.withUsername("user")
                .password("{noop}user")
// tag::snippet[]
                .roles(Roles.USER)
// end::snippet[]
                .build();
        var admin = User.withUsername("admin")
                .password("{noop}admin")
// tag::snippet[]
                .roles(Roles.ADMIN)
// end::snippet[]
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
```

Secure the Task Service

In an earlier tutorial, you made the task list read-only for users, allowing only admins to create tasks.

Open `TaskService` and replace `@AnonymousAllowed` with `@PermitAll`. Then, add `@RolesAllowed` to `createTask()`:

TaskService.java

```java
@BrowserCallable
// tag::snippet[]
@PermitAll
// end::snippet[]
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TaskService {
    ...

// tag::snippet[]
    @RolesAllowed(Roles.ADMIN)
// end::snippet[]
    public void createTask(String description, @Nullable LocalDate dueDate) {
        //...
    }

    public List<Task> list(Pageable pageable) {
        // ...
    }
}
```

Restart the application and open your browser at: <http://localhost:8080>

Log in as `ADMIN` and create some tasks. Everything should work as before.

Break the Task List

To see that the service is actually protected, you’re going to break the task list. Open `src/main/frontend/views/@index.tsx` and change `TaskListView()` so that `isAdmin` is always `true`:

frontend/views/@index.tsx

```tsx
...

export default function TaskListView() {
    const dataProvider = useDataProvider<Task>({
        list: (pageable) => TaskService.list(pageable),
    });
    const auth = useAuth();
// tag::snippet[]
    const isAdmin = true; // auth.hasAccess({ rolesAllowed: ["ADMIN"] });
// end::snippet[]
    // ..
}
```

Then go back to the browser, logout, and login as `USER`. If you now try to create a task, you should get an error message.

Now change `TaskListView()` back again.

Final Thoughts

Your Vaadin application now has both secure views and secure services. However, it still uses **in-memory authentication**. You should replace it with a stronger storage mechanism.

> **Note:** A guide showing you how to do this in a Vaadin application is planned, but not yet written. In the meantime, refer to the [Spring Security Reference Manual](https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/storage.html).
