Loading...
Loading...
Guide Claude on securing Vaadin 25 applications with Spring Security. This skill should be used when the user asks to "add security", "add login", "create a login view", "create a login form", "use Spring Security", "secure a view", "add authentication", "add authorization", "use @RolesAllowed", "use @PermitAll", "use @AnonymousAllowed", "use @DenyAll", "use VaadinSecurityConfigurer", "add OAuth2", "use OAuth2 login", "use Google login", "use Keycloak", "use GitHub login", "add logout", "add a logout button", "use AuthenticationContext", "protect a view", "role-based access", "configure SecurityFilterChain", or needs help with view access control, login forms, OAuth2 providers, or logout handling in Vaadin Flow.
npx skill4agent add vaadin/agent-skills securitysearch_vaadin_docsget_component_java_apiget_component_stylingvaadin_version"25"ui_language"java"VaadinSecurityConfigurerLoginForm@AnonymousAllowed@PermitAll@RolesAllowed@DenyAllAuthenticationContextviews-and-navigation@Route@LayoutAppLayoutSideNavclient-side-viewsViewConfig.loginRequiredViewConfig.rolesAllowed@BrowserCallable<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>VaadinSecurityConfigurer@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
configurer.loginView(LoginView.class);
});
return http.build();
}
@Bean
public UserDetailsManager userDetailsManager() {
// WARNING: In-memory users for development only.
// Use JDBC, LDAP, or OAuth2 in production.
var user = User.withUsername("user")
.password("{noop}user")
.roles("USER")
.build();
var admin = User.withUsername("admin")
.password("{noop}admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}VaadinSecurityConfigurer.vaadin()LoginForm@Route(value = "login", autoLayout = false)
@PageTitle("Login")
@AnonymousAllowed
public class LoginView extends Main implements BeforeEnterObserver {
private final LoginForm login;
public LoginView() {
login = new LoginForm();
login.setAction("login");
addClassNames(LumoUtility.Display.FLEX,
LumoUtility.JustifyContent.CENTER,
LumoUtility.AlignItems.CENTER);
setSizeFull();
add(login);
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
if (event.getLocation()
.getQueryParameters()
.getParameters()
.containsKey("error")) {
login.setError(true);
}
}
}autoLayout = falseAppLayout@AnonymousAllowedlogin.setAction("login")/loginBeforeEnterObserver?errorconfigurer.loginView(LoginView.class)| Annotation | Access Level | Typical Use |
|---|---|---|
| Anyone (no login required) | Login view, public landing page |
| Any authenticated user | Dashboard, user profile |
| Users with specified role(s) | Admin panel, user management |
| Nobody | Default when no annotation is present |
Note on: Vaadin's use of@PermitAlldiffers from the Jakarta Security standard. In standard Jakarta Security,@PermitAllmeans "anyone, including unauthenticated users" — similar to Vaadin's@PermitAll. In Vaadin,@AnonymousAllowedmeans "any authenticated user." Developers familiar with standard Jakarta security may be confused when access is denied to unauthenticated users on a view they explicitly "permitted all" — use@PermitAllfor truly public views.@AnonymousAllowed
@Route("public")
@AnonymousAllowed
public class PublicView extends VerticalLayout { }
@Route("dashboard")
@PermitAll
public class DashboardView extends VerticalLayout { }
@Route("admin")
@RolesAllowed("ADMIN")
public class AdminView extends VerticalLayout { }@DenyAll@PermitAll@DenyAll@DenyAll@AnonymousAllowed@RolesAllowed@PermitAll@RolesAllowedpublic final class Roles {
public static final String ADMIN = "ADMIN";
public static final String USER = "USER";
private Roles() {
}
}
// Usage:
@RolesAllowed(Roles.ADMIN)
public class AdminView extends VerticalLayout { }AuthenticationContext@Route("settings")
@PermitAll
public class SettingsView extends VerticalLayout {
public SettingsView(AuthenticationContext authContext) {
authContext.getAuthenticatedUser(UserDetails.class)
.ifPresent(user -> add(new H2("Welcome " + user.getUsername())));
if (authContext.hasRole(Roles.ADMIN)) {
add(new Button("Admin Settings", event -> {
// show admin-only settings
}));
}
}
}AuthenticationContextMainLayoutpublic class MainLayout extends AppLayout {
private final transient AuthenticationContext authContext;
public MainLayout(AuthenticationContext authContext) {
this.authContext = authContext;
var title = new H1("My App");
title.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
var logout = new Button("Logout", event -> authContext.logout());
var header = new HorizontalLayout(title, logout);
header.setWidthFull();
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
header.setAlignItems(FlexComponent.Alignment.CENTER);
header.addClassNames(LumoUtility.Padding.Horizontal.MEDIUM);
addToNavbar(header);
}
}AuthenticationContexttransientSerializable/configurer.loginView(LoginView.class, "/goodbye");AuthenticationContextSecurityContextLogoutHandlerpublic void logout() {
UI.getCurrent().getPage().setLocation("/");
var logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.logout(
VaadinServletRequest.getCurrent().getHttpServletRequest(),
null, null);
}<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=openid,profile,emailspring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=my-client-id
spring.security.oauth2.client.registration.keycloak.client-secret=${KEYCLOAK_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid,profile
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://keycloak.local:8180/realms/my-apploginView()oauth2LoginPage()@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
configurer.oauth2LoginPage(
"/oauth2/authorization/google",
"{baseUrl}");
});
return http.build();
}
}{baseUrl}{baseScheme}{baseHost}{basePort}{basePath}LoginViewAuthenticationContext.logout()VaadinSecurityConfigurerapplication.propertiesoauth2LoginPage()/oauth2/authorization/{registrationId}{registrationId}application.propertiesclient-idclient-secretscopeissuer-uriauthorization-grant-typeUserDetailsManager{noop}@DenyAllAuthenticationContexttransientautoLayout = falseVaadinSecurityConfigurerapplication.properties${GOOGLE_CLIENT_SECRET}Rolespublic static final String@RolesAllowedautoLayout = false@PermitAll@DenyAll@Secured@PreAuthorize@AnonymousAllowed@PermitAll@RolesAllowed@DenyAllSecurityConfig{noop}SecurityContextLogoutHandlerUI.getCurrent().getPage().setLocation()AuthenticationContext.logout()HttpSecurity.requestMatchers("/admin/**").hasRole("ADMIN")@RolesAllowed@PermitAllreferences/security-patterns.md