Getting started with User Manager
This guide explains two ways to start using User Manager: generating a new application with User Manager already integrated, or integrating it by hand into an existing application.
Quick Start with Craft
The fastest way to try User Manager is to generate a new application with Craft, the AppJars project generator. Craft produces a runnable Spring Boot and Vaadin project with User Manager already integrated, so none of the manual integration steps are needed.
- Open craft.appjars.com and select the User Manager card in the Modules section. Other AppJars can be selected as well; Craft wires them together.
- Adjust the Project and Appearance settings if needed, and click Download project.
- Extract the downloaded archive and run
docker compose up -dfrom the extracted folder. The first start takes a few minutes while the application image is built. To run the application with Maven instead, runmvn spring-boot:run. - Open
http://localhost:8080. The application shows a login page. Sign in with the usernameadminand the passwordadmin; the User Manager views are in the navigation drawer.
Craft also generates the Spring Security configuration and the login view. External authentication providers need additional settings, described in Optional: Enabling External Authentication Providers.
The Craft Project Generator page describes every option and the contents of the generated project. Continue with Testing the Application to try User Manager.
Manual Integration
Prepare the Application
The following steps integrate User Manager into an existing Spring Boot and Vaadin application. To follow them from a blank application, open Craft, leave every module unselected, and click Download project. Extract the archive, import it into your IDE of choice, and verify the application starts correctly by running the main class. A project generated by Craft already declares the AppJars repository, so the next step can be skipped.
Add the AppJars Repository
AppJars artifacts are published to the public AppJars Maven repository. Add it to your pom.xml so that Maven can resolve the AppJars dependencies:
<repositories>
<repository>
<id>appjars</id>
<name>AppJars Public Repository</name>
<url>https://maven.appjars.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
Add the Dependencies
Given that it is a monolithic application, the following dependencies representing the three layers of the appjar must be added:
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-user-manager-flow</artifactId>
</dependency>
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-user-manager-data-impl</artifactId>
</dependency>
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-user-manager-business-impl</artifactId>
</dependency>
After adding them, build the application to confirm that the dependencies are resolved correctly.
Modifying the Main Application Class
The following annotations must be added to the main application class:
@EnableVaadin({"com.appjars.usermanager.flow", "com.example.demo"})
@ComponentScan(basePackageClasses = {UserManagerAutoConfiguration.class, AppJarsAutoConfiguration.class, Application.class})
- @EnableVaadin: Instructs Vaadin to process the specified packages for views and components. The first entry covers the User Manager package; the second covers the application's own views.
- @ComponentScan: Instructs Spring to load the beans provided by the appjar as well as the beans of the application itself.
The imports for the classes referenced in @ComponentScan are:
com.appjars.usermanager.UserManagerAutoConfigurationcom.appjars.AppJarsAutoConfiguration
If the application already contains JPA entities, add an @EntityScan annotation that includes both the application's entity packages and the User Manager package class:
@EntityScan(basePackageClasses = {UserManagerModule.class, YourEntity.class})
The import for UserManagerModule is com.appjars.usermanager.UserManagerModule.
The dataSourceScriptDatabaseInitializer bean can be removed from the application if present, as User Manager handles database initialisation.
Then inject the RouteConfigurer provided by User Manager and configure the router layout in a @PostConstruct method so that the appjar views share the same layout as the rest of the application:
@Autowired
@Qualifier("UserManagerRouteConfigurer")
RouteConfigurer routeConfigurer;
@PostConstruct
public void configure() {
routeConfigurer.setViewsRouterLayout(MainLayout.class);
}
The import for RouteConfigurer is com.appjars.usermanager.flow.util.RouteConfigurer.
Configure Spring Security
Modify the SecurityConfiguration class to integrate with User Manager. User Manager provides its own PasswordEncoder, UserDetailsService, and a RememberMeServicesProvider bean, so those no longer need to be declared in the application.
Replace the contents of SecurityConfiguration with the following:
@EnableWebSecurity
@Configuration
public class SecurityConfiguration {
public static final String LOGIN_URL = "/login";
public static final String LOGOUT_URL = "/";
private final RememberMeServicesProvider rememberMeServicesProvider;
public SecurityConfiguration(RememberMeServicesProvider rememberMeServicesProvider) {
this.rememberMeServicesProvider = rememberMeServicesProvider;
}
@Bean
static NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
return new NavigationAccessControlConfigurer()
.withAvailableNavigationAccessCheckers(
checker -> checker instanceof UserManagerNavigationAccessChecker);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Public paths must be declared before calling vaadin(), which appends a final
// anyRequest matcher.
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login*").permitAll()
.requestMatchers(HttpMethod.GET, "/images/**", "/icons/**", "/*.css", "/*.png")
.permitAll());
http.formLogin(formLogin -> formLogin
.loginPage(LOGIN_URL)
.permitAll()
.failureHandler(authenticationFailureHandler()));
http.rememberMe(rememberMe -> rememberMe
.rememberMeServices(rememberMeServicesProvider.getRememberMeServices())
.tokenValiditySeconds(7200));
http.with(vaadin(), configurer -> configurer.loginView(LoginView.class, LOGOUT_URL));
return http.build();
}
public AuthenticationFailureHandler authenticationFailureHandler() {
Map<String, String> exceptionMappings = new HashMap<>();
exceptionMappings.put(DisabledException.class.getCanonicalName(), "/login?error=disabled");
exceptionMappings.put(BadCredentialsException.class.getCanonicalName(), "/login?error=badcredentials");
ExceptionMappingAuthenticationFailureHandler result =
new ExceptionMappingAuthenticationFailureHandler();
result.setExceptionMappings(exceptionMappings);
result.setDefaultFailureUrl("/login?error");
return result;
}
}
The vaadin() method is a static import from com.vaadin.flow.spring.security.VaadinSecurityConfigurer. The import for RememberMeServicesProvider is com.appjars.usermanager.service.util.RememberMeServicesProvider.
Key points about this configuration:
- SecurityFilterChain: Vaadin 25 configures security through a
SecurityFilterChainbean and theVaadinSecurityConfigurerreturned byvaadin(). TheVaadinWebSecuritybase class used by earlier versions has been removed, so a configuration that extends it no longer compiles. - Declaration order:
vaadin()appends a matcher foranyRequest, andauthorizeHttpRequestsrules are first-match in declaration order. Every rule that permits a public path must therefore be declared beforehttp.with(vaadin(), …). - NavigationAccessControlConfigurer: Required for User Manager's runtime access rules to take effect. This bean registers
UserManagerNavigationAccessChecker, which evaluates the rules configured at runtime against incoming navigation requests. - RememberMeServicesProvider: Provided by User Manager. It wires together the
UserDetailsServiceandPersistentLoginServiceneeded for persistent login support. - formLogin: Naming the login page is what keeps Spring Security from generating a login page of its own. Without it, the generated page answers
GET /loginand the application's ownLoginViewroute never renders. - authenticationFailureHandler: Maps specific Spring Security exceptions to login error URLs so the login view can display appropriate messages for disabled accounts and bad credentials.
Public paths declared by the appjar
Two User Manager routes are public by nature: the registration link and the password reset link. Whoever opens them either has no account yet or cannot sign in, so they must be reachable without authentication. The same applies to the external authentication entry points.
User Manager declares those paths permitAll() on every HttpSecurity, so the application does not have to do anything for them to work. The declaration is registered while Spring Security creates the HttpSecurity bean, which happens before the application's own SecurityFilterChain method runs, so it survives a broad matcher over the appjar's URL space:
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/um/**").authenticated());
The permitted paths are the configured registration link and password reset link URLs together with their subpaths, plus /oauth2/authorization/**, /login/oauth2/code/**, and /um/external-auth-start/**.
Permitting a path only means that Spring Security lets the request through. The registration and password reset views still reject an invalid or expired link code. An application that needs to protect these paths itself can set com.appjars.usermanager.security.permit-public-links to false.
Use User Manager Services and Views
User Manager provides its own user model and services. Update the application to use them instead of the generated ones.
Replace the AuthenticatedUser class with the following implementation that uses UserService and UserDto:
@Component
public class AuthenticatedUser {
private final UserService userService;
private final AuthenticationContext authenticationContext;
public AuthenticatedUser(AuthenticationContext authenticationContext, UserService userService) {
this.userService = userService;
this.authenticationContext = authenticationContext;
}
@Transactional
public Optional<UserDto> get() {
return authenticationContext.getAuthenticatedUser(UserDetails.class)
.map(userDetails -> userService.findByUsername(userDetails.getUsername()))
.orElse(Optional.empty());
}
public void logout() {
authenticationContext.logout();
}
}
The imports are:
com.appjars.usermanager.model.UserDtocom.appjars.usermanager.service.UserService
Because authenticatedUser.get() now returns Optional<UserDto>, update the createFooter() method in MainLayout accordingly:
private Footer createFooter() {
Footer layout = new Footer();
Optional<UserDto> maybeUser = authenticatedUser.get();
if (maybeUser.isPresent()) {
UserDto user = maybeUser.get();
Avatar avatar = new Avatar(user.getUsername());
avatar.setThemeName("xsmall");
avatar.getElement().setAttribute("tabindex", "-1");
MenuBar userMenu = new MenuBar();
userMenu.setThemeName("tertiary-inline contrast");
MenuItem userName = userMenu.addItem("");
Div div = new Div();
div.add(avatar);
div.add(user.getUsername());
div.add(new Icon("lumo", "dropdown"));
div.getElement().getStyle().set("display", "flex");
div.getElement().getStyle().set("align-items", "center");
div.getElement().getStyle().set("gap", "var(--lumo-space-s)");
userName.add(div);
userName.getSubMenu().addItem("Sign out", e -> authenticatedUser.logout());
layout.add(userMenu);
} else {
Anchor loginLink = new Anchor("login", "Sign in");
layout.add(loginLink);
}
return layout;
}
Add the User Manager views to the navigation menu. Insert the following snippet at the end of createNavigation() in MainLayout, before returning nav:
if (accessChecker.hasAccess(UsersListView.class)) {
SideNavItem navSec = new SideNavItem("Security");
navSec.setPrefixComponent(VaadinIcon.SHIELD.create());
if (accessChecker.hasAccess(UsersListView.class)) {
navSec.addItem(new SideNavItem("Users", UsersListView.class, VaadinIcon.USER.create()));
}
if (accessChecker.hasAccess(AuthoritiesView.class)) {
navSec.addItem(new SideNavItem("Roles", AuthoritiesView.class, VaadinIcon.KEY.create()));
}
if (accessChecker.hasAccess(GroupsListView.class)) {
navSec.addItem(new SideNavItem("Groups", GroupsListView.class, VaadinIcon.USERS.create()));
}
if (accessChecker.hasAccess(RulesView.class)) {
navSec.addItem(new SideNavItem("Rules", RulesView.class, VaadinIcon.SCALE.create()));
}
if (accessChecker.hasAccess(ViewsView.class)) {
navSec.addItem(new SideNavItem("Views", ViewsView.class, VaadinIcon.EYE.create()));
}
if (accessChecker.hasAccess(ProfileView.class)) {
navSec.addItem(new SideNavItem("My Profile", ProfileView.class, VaadinIcon.USER_CARD.create()));
}
nav.addItem(navSec);
}
The imports for the view classes are:
com.appjars.usermanager.flow.view.UsersListViewcom.appjars.usermanager.flow.view.AuthoritiesViewcom.appjars.usermanager.flow.view.GroupsListViewcom.appjars.usermanager.flow.view.RulesViewcom.appjars.usermanager.flow.view.ViewsViewcom.appjars.usermanager.flow.view.ProfileView
The icons come from com.vaadin.flow.component.icon.VaadinIcon, which ships with Vaadin, so they need no additional dependency.
These views are annotated with @PermitAll, so any authenticated user can open them until access rules or the application's security configuration restrict them. See Restricting Access to AppJar Views.
Finally, simplify the LoginView so it extends the view provided by User Manager:
@AnonymousAllowed
@Route("login")
public class LoginView extends UserManagerLoginView {}
The import for UserManagerLoginView is com.appjars.usermanager.flow.view.UserManagerLoginView.
Remove Unneeded Classes
If the application already defines its own user model and authentication, such as a User entity, a Role enumeration, a UserDetailsService implementation, or the repository and service that back them, remove those classes, as User Manager provides equivalents. After removing them, resolve any resulting import errors in the classes that referenced them.
Likewise, remove any SQL script that creates or populates user tables, such as src/main/resources/data.sql. User Manager handles the creation and initial population of its database tables. A project generated by Craft contains none of these classes, so this step can be skipped.
Configure Application Properties
Add the following properties to application.properties:
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
Finally, add com.flowingcode and com.appjars to the list of whitelisted packages:
vaadin.allowed-packages = com.vaadin,org.vaadin,com.example.demo,com.flowingcode,com.appjars
Testing the Application
Start the application by running the main Spring Boot class. User Manager creates and populates the database tables on first run. After the application starts, log in using the username admin and password admin.
The Security menu group will be visible in the navigation, providing access to user, role, group, rule, and view management. Navigate to My Profile to verify that the authenticated user's details are displayed correctly.
Optional: Enabling External Authentication Providers
External authentication providers (Google, GitHub, or any provider written against the User Manager SPI) are optional. The steps below are only required when the application needs to offer OAuth2 or OpenID Connect sign-in alongside, or instead of, the regular username and password form.
Add the OAuth2 Client Dependency
External providers are wired through Spring Security's OAuth2 client. Add the starter to the application's POM:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
When this dependency is not present, all external-provider beans declared by User Manager remain dormant and the rest of the appjar continues to function with regular authentication only.
Wire the External Auth Configurer
User Manager exposes an ExternalAuthSecurityConfigurer bean that registers the OAuth2 login filters, the database-backed client registration repository, and the success/failure handlers required by the appjar. The bean is autowired as an optional dependency, so the security configuration compiles and runs whether or not the OAuth2 client dependency is present.
Update SecurityConfiguration to call the configurer before http.with(vaadin(), …):
@Autowired(required = false)
private ExternalAuthSecurityConfigurer externalAuthSecurityConfigurer;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// ... existing http.authorizeHttpRequests / http.formLogin / http.rememberMe calls ...
if (externalAuthSecurityConfigurer != null) {
externalAuthSecurityConfigurer.configure(http);
}
http.with(vaadin(), configurer -> configurer.loginView(LoginView.class, LOGOUT_URL));
return http.build();
}
The import is com.appjars.usermanager.flow.security.ExternalAuthSecurityConfigurer.
Set the External URL
OAuth2 providers require the application's redirect URI to be registered in their developer console. The redirect URI shown in the administration view is built from the com.appjars.usermanager.external-url property:
com.appjars.usermanager.external-url=https://app.example.com
For local development the default http://localhost:8080 is sufficient. In production this must be the public-facing base URL of the application; the value is also used to build the registration-link URLs sent over email.
Configure the Client Secret Encryption Key
Each provider's client secret is stored encrypted in the database. User Manager encrypts and decrypts these values with an AES-256 key supplied through the com.appjars.usermanager.external-auth.secret.key property:
com.appjars.usermanager.external-auth.secret.key=<base64-encoded 32-byte key>
The value must be a base64-encoded 32-byte key — that is, exactly 44 base64 characters that decode to 32 raw bytes. An arbitrary passphrase will not work; it must decode to precisely 32 bytes. Generate a suitable key with:
openssl rand -base64 32
The property has no default. It is only required when external authentication is used and is validated the first time a client secret is encrypted or decrypted, so an application that does not use external providers can start without it. When the key is missing or blank, registering a provider fails with an error indicating the property is not configured.
Warning
Keep this key stable and secret. Changing it after providers have been saved makes the existing encrypted client secrets undecryptable, and those providers must be re-entered.
Configure Providers at Runtime
With the configurer wired and the application restarted, administrators can register one or more providers through the Authentication Providers view under the Security menu group. The view accepts the client ID and client secret obtained from each provider's developer console, along with the OAuth scopes and an optional default group for users created by the provider. See Authentication Providers in the user guide for the full administration flow.
Optional: Disable Regular Authentication
If the application should rely exclusively on external providers, the Regular entry in the Authentication Providers view can be disabled. User Manager automatically hides the username and password fields on the login and registration pages, and gates registration links, password reset links, and the change password view behind a friendly notification.
Warning
At least one provider must remain enabled at all times. The administration view refuses to disable or delete the last enabled provider.