Implementing Authentication and Authorization With Vaadin
Learn how to implement a login screen and control access to the application based on user roles
Join the DZone community and get the full member experience.
Join For FreeThis article shows how to implement authentication and authorization in Spring Boot Vaadin Flow applications without using Spring Security.
Why Without Spring Security?
Spring Security is a powerful framework designed for web applications based on the request/response paradigm. Vaadin hides this paradigm so you can focus on the application and UI logic without having to deeply understand the underlying web technologies. Although it's possible to use Spring Security with Vaadin, doing so requires more work than the approach shown in this article.
Creating a New Project
Start by creating a new Vaadin application with three views:
- Login (
/login
) - Home (
/home
) - Admin (
/admin
)
Use Java-only views and select the template you want for each view.
The Data Model
Create a Role
enum to encapsulate the different roles that users can have in the application as follows:
public enum Role {
USER, ADMIN
}
Create a User
class to encapsulate the login data:
x
public class User extends AbstractEntity {
private String username;
private String passwordSalt;
private String passwordHash;
private Role role;
public User() {
}
public User(String username, String password, Role role) {
this.username = username;
this.role = role;
this.passwordSalt = RandomStringUtils.random(32);
this.passwordHash = DigestUtils.sha1Hex(password + passwordSalt);
}
public boolean checkPassword(String password) {
return DigestUtils.sha1Hex(password + passwordSalt).equals(passwordHash);
}
... getters and setters ...
}
This implementation uses Apache Commons Codec to encrypt the password and Apache Commons Lang to generate a random salt string. This makes it harder to hack passwords using "dictionary attacks" and avoids compromising users with the same password. The implementation also uses the AbstractEntity class included in the generated project. This class contains an id
field and equals
and hashCode
implementations suitable for a JPA entity.
To gain access to the database, add the following Spring Data repository interface:
xxxxxxxxxx
public interface UserRepository extends JpaRepository<User, Integer> {
User getByUsername(String username);
}
Notice that you don't have to implement this interface since Spring Data will create an implementation at runtime. The interface offers many other useful methods that you should get familiar with.
Implementing the Authentication Service
Create a new AuthService
class to encapsulate the authentication and authorization logic:
xxxxxxxxxx
public class AuthService {
public record AuthorizedRoute(String route, String name, Class<? extends Component> view) {
}
public class AuthException extends Exception {
}
private final UserRepository userRepository;
public AuthService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void authenticate(String username, String password) throws AuthException {
User user = userRepository.getByUsername(username);
if (user != null && user.checkPassword(password)) {
VaadinSession.getCurrent().setAttribute(User.class, user);
createRoutes(user.getRole());
} else {
throw new AuthException();
}
}
private void createRoutes(Role role) {
getAuthorizedRoutes(role).stream()
.forEach(route ->
RouteConfiguration.forSessionScope().setRoute(
route.route, route.view, MainView.class));
}
public List<AuthorizedRoute> getAuthorizedRoutes(Role role) {
var routes = new ArrayList<AuthorizedRoute>();
if (role.equals(Role.USER)) {
routes.add(new AuthorizedRoute("home", "Home", HomeView.class));
routes.add(new AuthorizedRoute("logout", "Logout", LogoutView.class));
} else if (role.equals(Role.ADMIN)) {
routes.add(new AuthorizedRoute("home", "Home", HomeView.class));
routes.add(new AuthorizedRoute("admin", "Admin", AdminView.class));
routes.add(new AuthorizedRoute("logout", "Logout", LogoutView.class));
}
return routes;
}
}
The authenticate
method tries to find a user in the database with the given credentials and if it succeeds, a reference to the user is stored in the session and the available routes (links to views in the application) are created for the corresponding user role. If no user is found, an exception is thrown so that the view can show a suitable message.
The createRoutes
method takes the routes available to the specified role and adds them to the route configuration of Vaadin, making them accessible. The getAuthorizedRoutes
method in this example builds a list with the routes for a role in a "hard-coded" fashion. Other implementations may build this list from an external source like a database, for example.
Implementing the Login View
You can use any available components to implement a login form. For example, you can use the LoginForm
class or alternatively create a custom implementation like the following:
x
value = "login") (
"Login") (
"./styles/views/login/login-view.css") (
public class LoginView extends Div {
public LoginView(AuthService authService) {
setId("login-view");
var username = new TextField("Username");
var password = new PasswordField("Password");
add(
new H1("Welcome"),
username,
password,
new Button("Login", event -> {
try {
authService.authenticate(username.getValue(), password.getValue());
UI.getCurrent().navigate("home");
} catch (AuthService.AuthException e) {
Notification.show("Wrong credentials.");
}
})
);
}
}
The view calls the service to perform the actual authentication and authorization setup logic (registering the routes available to the user).
Summary
You can find the complete source code at https://github.com/alejandro-du/vaadin-auth-example. Keep in mind that this code is intended to be an example of how you can use the RouteConfiguration
class to register routes at runtime for authorization reasons and might not be production-ready. For instance, a known issue with the example is that users can't use the log-in view to authenticate again (or with a different user) if they are already logged in. I hope this example serves as a starting point for implementing the authentication and authorization logic in your Vaadin Flow applications.
Opinions expressed by DZone contributors are their own.
Comments