> Markdown version of [Protect Views](https://vaadin.com/docs/next/building-apps/security/protect-views). Section index: [llms.txt](https://vaadin.com/docs/next/building-apps/llms.txt)

# Protect Views

Logging in and out ensures that only **authenticated users** can access the application. However, most business applications require more than just authentication—different types of users often have varying levels of access.

In role-based applications, users are assigned specific *roles* that determine what data they can access and which actions they can perform. To enforce these restrictions, you must implement proper **authorization** for your views.

## <a id="declarative-view-security"></a>Declarative View Security

The easiest way to grant or deny access to a Vaadin 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 views are inaccessible unless explicitly annotated**.

> **Note: Different Security Annotations**
>
> 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:

```java
@Route("public")
@PageTitle("Public View")
// tag::snippet[]
@AnonymousAllowed
// end::snippet[]
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:

```java
@Route("private")
@PageTitle("Private View")
// tag::snippet[]
@PermitAll
// end::snippet[]
public class PrivateView extends Main {
    ...
}
```

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

```java
@Route("admin")
@PageTitle("Admin View")
// tag::snippet[]
@RolesAllowed("ADMIN")
// end::snippet[]
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.

> **Note: Error Views Need Security Annotations Too**
>
> Custom error handler views — such as a class extending `RouteNotFoundError` — are also subject to access control. Add an appropriate security annotation (e.g., `@AnonymousAllowed`) to ensure they are accessible. With Spring Security, a custom not-found view additionally requires relaxing the catch-all request rule, so that requests for non-existing pages reach the application instead of being denied at the HTTP layer. See [Router Exception Handling](https://vaadin.com/docs/next/flow/routing/exceptions.md) for more details.

### <a id="annotation-inheritance"></a>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:

```java
@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`.

## <a id="programmatic-view-security"></a>Programmatic View Security

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

### <a id="making-a-custom-navigation-access-checker"></a>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`:

```java
@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 more information 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.

### <a id="enabling-a-navigation-access-checker"></a>Enabling a Navigation Access Checker

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

`SecurityConfig.java`

```java
@EnableWebSecurity
@Configuration
class SecurityConfig {

    @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](https://vaadin.com/docs/next/flow/security/advanced-topics/navigation-access-control.md).

## <a id="controlling-access-within-views"></a>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.

### <a id="checking-the-roles-of-the-current-user"></a>Checking the Roles of the Current User

**To check the roles of the current user**, inject an `AuthenticationContext` object into your view:

```java
@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.

### <a id="leaving-out-hiding-and-restricting-components"></a>Leaving Out, Hiding, and Restricting Components

Once you know the roles of the current user, you build the view to match them. The safest option is to **not create the component at all**. A component that’s never added to the view can’t be interacted with, and nothing about it ever reaches the browser:

```java
public CustomerView(AuthenticationContext authenticationContext) {
    var grid = new Grid<>(Customer.class);
    add(grid);
    if (authenticationContext.hasRole("ADMIN")) {
        add(new Button("Delete", event -> deleteSelected(grid))); // (1)
    }
}
```

1. Only administrators get a **Delete** button.

When the component has to exist — because other code refers to it, or because you change its state while the view is open — restrict it instead. Use `setVisible(false)` to take it out of the UI altogether:

```java
var deleteButton = new Button("Delete", event -> deleteSelected(grid));
deleteButton.setVisible(authenticationContext.hasRole("ADMIN")); // (1)
```

1. Users without the `ADMIN` role don’t see the button at all.

Use `setEnabled(false)` when users should still see that an action exists, even though they can’t perform it:

```java
var approveButton = new Button("Approve", event -> approveSelected(grid));
approveButton.setEnabled(authenticationContext.hasRole("APPROVER")); // (1)
```

1. Everyone sees the button, but only users with the `APPROVER` role can click it.

For **input fields**, prefer `setReadOnly(true)` to disabling. A read-only field keeps its value legible and selectable, whereas a disabled field is dimmed and awkward to read:

```java
var salaryField = new TextField("Salary");
salaryField.setReadOnly(!authenticationContext.hasRole("ADMIN")); // (1)
```

1. Users without the `ADMIN` role see the salary, but can’t change it.

When the fields belong to a `Binder`, `Binder.setReadOnly(true)` applies the same to every bound field at once. To make a single field permanently read-only, bind it with `Binder.bindReadOnly()`.

**All three methods are enforced on the server**, not only in the browser. Flow stops sending updates to an invisible component, and ignores events and property changes that arrive from a component that’s invisible or disabled. A read-only field rejects a value that arrives from the client and restores the one it had. A user who makes the element visible, enabled, or writable again with the browser’s developer tools therefore still can’t get past the check.

They differ in scope, though. `setVisible(false)` and `setEnabled(false)` block everything the component could send to the server, whereas `setReadOnly(true)` guards only the field’s value. Hide or disable a component when nothing about it should be reachable.

One exception is worth knowing about: a component can reopen the client-to-server channel for its disabled state with `DisabledUpdateMode.ALWAYS`. If you do that in a component of your own, `setEnabled(false)` no longer blocks everything. See [Overriding Default Disabled Behavior](https://vaadin.com/docs/next/flow/component-internals/enabled-state.md#overriding-default-disabled-behavior) for details.

Setting visibility or the enabled state on a container applies it to the children as well. A single call therefore hides or disables a whole section of a view. For the full semantics of both states, see [Component Visibility](https://vaadin.com/docs/next/flow/component-internals/visibility.md) and [Component Enabled State](https://vaadin.com/docs/next/flow/component-internals/enabled-state.md).

> **Warning: Don’t Use CSS to Restrict Access**
>
> Never restrict access with CSS — such as `display: none`, `visibility: hidden`, or `pointer-events: none` — or by setting the `hidden`, `disabled`, or `readonly` attribute directly on the element. These only change how the component looks and behaves in the browser. On the server, the component is still visible, enabled, and writable, and it keeps accepting events, so anyone can restore it from the browser’s developer tools and use it. Use CSS for styling. Use `setVisible()`, `setEnabled()`, and `setReadOnly()` for access.

### <a id="hiding-isnt-a-security-boundary"></a>Hiding Isn’t a Security Boundary

Leaving out, hiding, disabling, and write-protecting components decides **what users can reach through the UI**. It doesn’t decide what they can do to your data. A bug in the view, a component you forget to hide, or a later refactoring can all expose an action you meant to restrict.

Always enforce the same rules in the **services** the view calls, as described in the [Protect Services](https://vaadin.com/docs/next/building-apps/security/protect-services.md) guide. Treat the checks inside the view as a way to give users a relevant UI, and the checks in the services as the actual security boundary.

If a view still calls a service method that the current user isn’t allowed to call, Spring Security throws an `AccessDeniedException`. This points to a bug in the view, since the view shouldn’t offer an action that the user can’t perform. Unless the view catches the exception, Vaadin passes it to the session’s error handler. To show the user a clear message rather than a generic error, install a custom error handler that recognizes the exception, as described in [Handle Errors](https://vaadin.com/docs/next/building-apps/ui-basics/handle-errors.md#access-denied-by-a-service). The same guide also shows how to customize what users see when they navigate to a view that they aren’t allowed to access.

## <a id="role-constants"></a>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:

Roles.java

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

```java
@Route
// tag::snippet[]
@RolesAllowed(Roles.ADMIN)
// end::snippet[]
public class AdminView extends Main {
    ...
}
```

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

    public MyView(AuthenticationContext authenticationContext) {
// tag::snippet[]
        if (authenticationContext.hasRole(Roles.ADMIN)) {
// end::snippet[]
            // Set up the UI for an admin
        } else {
            // Set up the UI for normal users
        }
    }
    ...
}
```
