Access-Denied & Not-Found Views
When navigation access control denies navigation to a view, Vaadin shows an error view instead of the view. This page describes the default behavior, how to customize the access-denied view, and what a custom not-found view needs in a secured application.
Default Behavior
Users who aren’t authenticated are redirected to the log-in view. For authenticated users without permission, Vaadin uses the default error handler for denied access, RouteAccessDeniedError. It doesn’t render anything itself. It reroutes to the not-found error, so the user sees the same RouteNotFoundError view as for a route that doesn’t exist. As a security precaution, the message therefore doesn’t say whether the navigation target exists. In development mode, the view lists the available routes. In production mode, it shows by default a message that reads, Could not navigate to 'RequestedRouteName'.
Custom Access-Denied View
You can customize what users see when access is denied in three ways:
-
by extending
RouteAccessDeniedError; -
by implementing
HasErrorParameter<AccessDeniedException>; or -
by rerouting to a different error type with the
@AccessDeniedErrorRouterannotation.
An error view needs an access annotation of its own, so that navigation access control allows navigation to it. Use @PermitAll or @RolesAllowed. @AnonymousAllowed isn’t recommended, as it exposes information about access restrictions to anonymous users.
The following example extends RouteAccessDeniedError:
Source code
Java
@PermitAll 1
public class CustomAccessDeniedError extends RouteAccessDeniedError {
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<AccessDeniedException> parameter) {
getElement().setText("Nothing to see here, please move on");
return HttpStatusCode.FORBIDDEN.getCode();
}
}-
Overrides the
@AnonymousAllowedannotation thatRouteAccessDeniedErrorcarries, for the reason explained above.
The following example implements HasErrorParameter:
Source code
Java
@Tag(Tag.DIV)
@PermitAll
public static class CustomAccessDeniedError extends Component
implements HasErrorParameter<AccessDeniedException> {
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<AccessDeniedException> parameter) {
getElement().setText("Access denied.");
return HttpStatusCode.FORBIDDEN.getCode();
}
}The following example reroutes denied navigation to an administrative view to the not-found view, which is the default view for NotFoundException. Users without access then can’t tell whether the view exists:
Source code
Java
@Route(value = "admin", layout = MainLayout.class)
@PageTitle("Admin View")
@RolesAllowed("ADMIN")
@AccessDeniedErrorRouter(rerouteToError = NotFoundException.class)
public class AdminView extends VerticalLayout {
// ...
}Without rerouteToError, the annotation reroutes to the error view for AccessDeniedException. The exception class given in rerouteToError needs a public no-argument constructor.
Custom Not-Found View
A custom not-found view — a class extending RouteNotFoundError, or implementing HasErrorParameter<NotFoundException> — needs an access annotation, so that navigation access control allows it to render. The following example annotates the view with @PermitAll, so that signed-in users see it, and users who aren’t signed in are sent to the log-in view. Use @AnonymousAllowed instead if users who aren’t signed in can navigate in your application, for example between public views. If the view has a parent layout, set with @ParentLayout, the layout needs to allow the same users, or navigation access control blocks the view.
Source code
CustomNotFoundView.java
CustomNotFoundView.java@PermitAll
public class CustomNotFoundView extends RouteNotFoundError {
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<NotFoundException> parameter) {
getElement().setText("The page you're looking for doesn't exist.");
return HttpStatusCode.NOT_FOUND.getCode();
}
}With Spring Security
In an application secured with VaadinSecurityConfigurer, the not-found view also needs a relaxed catch-all request rule, so that requests for non-existing pages reach the Vaadin servlet. By default, VaadinSecurityConfigurer denies every request that isn’t explicitly allowed. A URL that doesn’t match any route, entered in the browser’s address bar or opened from a bookmark, is therefore denied by Spring Security before Vaadin can show the not-found view. The annotation doesn’t change this.
The security configuration then relaxes the catch-all rule with anyRequest():
Source code
SecurityConfiguration.java
SecurityConfiguration.java@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
configurer.loginView(LoginView.class)
.anyRequest(AuthorizedUrl::permitAll); 1
});
return http.build();
}-
Lets requests that match no other rule reach the Vaadin servlet, which shows the not-found view for them.
This doesn’t expose your views: they’re still protected at the HTTP layer by the request matchers of VaadinSecurityConfigurer, and by navigation access control. However, the catch-all rule also covers every other endpoint of the application, such as REST controllers or Spring Boot Actuator endpoints. Protect those with rules of their own before relaxing the catch-all rule, as rules added to http before the configurer are applied first.
If only signed-in users need to see the not-found view, use AuthorizedUrl::authenticated instead of AuthorizedUrl::permitAll. Users who aren’t signed in are then sent to the log-in view for any unknown URL, and other unmatched endpoints require authentication. See Security Configuration for the other options of anyRequest(), and Handle Errors for a guide to the error views of an application.