Documentation

Documentation versions (currently viewingVaadin 24)

Add a Login Screen to an Application

How to set up Spring Security to restrict access to views in a Flow application.

On this part of this tutorial, you’ll secure the Customer Relationship Management (CRM) application by setting up Spring Security and adding a login screen to limit access to logged-in users.

Login View

Start by creating a new view, LoginView, in the views package. You would do that like so:

package com.example.application.views;

import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;

@Route("login") // (1)
@PageTitle("Login | Vaadin CRM")
@AnonymousAllowed
public class LoginView extends VerticalLayout implements BeforeEnterObserver {

	private final LoginForm login = new LoginForm(); // (2)

	public LoginView(){
		addClassName("login-view");
		setSizeFull(); // (3)
		setAlignItems(Alignment.CENTER);
		setJustifyContentMode(JustifyContentMode.CENTER);

		login.setAction("login"); // (4)

		add(new H1("Vaadin CRM"), login);
	}

	@Override
	public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
		// inform the user about an authentication error
		if(beforeEnterEvent.getLocation()  // (5)
        .getQueryParameters()
        .getParameters()
        .containsKey("error")) {
            login.setError(true);
        }
	}
}
  1. Map the view to the "login" path. LoginView should encompass the entire browser window, so don’t use MainLayout as the parent.

  2. Instantiate a LoginForm component to capture username and password.

  3. Make LoginView full size and center its content — both horizontally and vertically — by calling setAlignItems(`Alignment.CENTER)` and setJustifyContentMode(`JustifyContentMode.CENTER)`.

  4. Set the LoginForm action to "login" to post the login form to Spring Security.

  5. Read query parameters and show an error if a login attempt fails.

Build the application and navigate to http://localhost:8080/login. You should see a centered login form like the one in the screenshot here:

Login view

Set Spring Security to Handle Logins

With the login screen in place, you now need to configure Spring Security to perform the authentication and to prevent unauthorized users from accessing views.

Installing Spring Security Dependencies

Add the Spring Security dependency in pom.xml like so:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Confirm that the dependency is downloaded. If you’re unsure, run ./mvnw install from the command line to download the dependency.

Configure Spring Security

Create a new package, com.example.application.security for classes related to security.

Tip
Create Classes Automatically

Paste the class code into the security package to have IntelliJ automatically create the class for you.

Enable and configure Spring Security with a new class, SecurityConfig.java like this:

package com.example.application.security;

import com.example.application.views.LoginView;
import com.vaadin.flow.spring.security.VaadinWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@EnableWebSecurity // (1)
@Configuration
public class SecurityConfig extends VaadinWebSecurity { // (2)

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth ->
                    auth.requestMatchers(
                        AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/images/*.png")).permitAll());  // (3)
        super.configure(http);
        setLoginView(http, LoginView.class); // (4)
    }

    @Bean
    public UserDetailsService users() {
        UserDetails user = User.builder()
                .username("user")
                // password = password with this hash, don't tell anybody :-)
                .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
                .roles("USER")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin); // (5)
    }
}
  1. Enable Spring Security.

  2. Extend the VaadinWebSecurity class to configure Spring Security for Vaadin.

  3. Allows public access to the image directory using GET requests.

  4. Allow access to LoginView.

  5. Configure an in-memory users for testing (see note below).

Warning
Never Use Hard-Coded Credentials in Production

Don’t use hard-coded credentials in real applications. You can change the Spring Security configuration to use an authentication provider for Lightweight Directory Access Protocol (LDAP), Java Authentication and Authorization Service (JAAS), and other real-world sources. Read more about Spring Security authentication providers.

Next, in the same package, create a service for accessing information on the logged-in user and for logging out the user.

package com.example.application.security;

import com.vaadin.flow.spring.security.AuthenticationContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

@Component
public class SecurityService {

    private final AuthenticationContext authenticationContext;

    public SecurityService(AuthenticationContext authenticationContext) {
        this.authenticationContext = authenticationContext;
    }

    public UserDetails getAuthenticatedUser() {
        return authenticationContext.getAuthenticatedUser(UserDetails.class).get();
    }

    public void logout() {
        authenticationContext.logout();
    }
}

Finally, add @PermitAll annotations to both views to allow all logged-in users to access them.

@PermitAll
@Route(value="", layout = MainLayout.class)
@PageTitle("Contacts | Vaadin CRM")
public class ListView extends VerticalLayout {
    // omitted
}
@PermitAll
@Route(value = "dashboard", layout = MainLayout.class)
@PageTitle("Dashboard | Vaadin CRM")
public class DashboardView extends VerticalLayout {
    // omitted
}

Add a Logout Button

You can now log in to the application. The last item to add is a logout button in the application header.

In MainLayout, add a link to the header like this:

package com.example.application.views;

import com.example.application.security.SecurityService;
import com.example.application.views.list.ListView;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.theme.lumo.LumoUtility;

public class MainLayout extends AppLayout {
    private final SecurityService securityService;

    public MainLayout(SecurityService securityService) {  // (1)
        this.securityService = securityService;
        createHeader();
        createDrawer();
    }

    private void createHeader() {
        H1 logo = new H1("Vaadin CRM");
        logo.addClassNames(
            LumoUtility.FontSize.LARGE,
            LumoUtility.Margin.MEDIUM);

        String u = securityService.getAuthenticatedUser().getUsername();
        Button logout = new Button("Log out " + u, e -> securityService.logout()); // (2)

        var header = new HorizontalLayout(new DrawerToggle(), logo, logout); // (3)

        header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
        header.expand(logo); // (4)
        header.setWidthFull();
        header.addClassNames(
            LumoUtility.Padding.Vertical.NONE,
            LumoUtility.Padding.Horizontal.MEDIUM);

        addToNavbar(header);

    }

    private void createDrawer() {
        addToDrawer(new VerticalLayout(
                new RouterLink("List", ListView.class),
                new RouterLink("Dashboard", DashboardView.class)
        ));
    }
}
  1. Autowire the SecurityService and save it in a field.

  2. Create a logout button that calls the logout() method in the service.

  3. Add the button to the header layout.

  4. Call header.expand(logo) to make the logo take up all of the extra space in the layout. This can push the logout button to the far right.

Stop and restart the server to get the new Maven dependencies. You should now be able to log in and out of the application. Verify that you can’t access http://localhost:8080/dashboard without being logged in. You can log in with the username, user, and the password, password.

Log out button on page

You have now built a full-stack CRM application with navigation and authentication. On the next part of this tutorial, you’ll learn how to turn it into a PWA to make it installable on mobile and desktop platforms.

migration assistance

Download free e-book.
The complete guide is also available in an easy-to-follow PDF format.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {
    this.renderBanner();
  }

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {
      return;
    }

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      );
      tocEl = document.createElement('div');
      tocEl.classList.add('toc');

      pageTitle?.insertAdjacentElement('afterend', tocEl);
    }

    // Prepare banner container
    bannerWrapper = document.createElement('div');
    bannerWrapper.id = 'tocBanner';
    tocEl?.appendChild(bannerWrapper);

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>
          </a>
        </div>`;

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {
      imgTarget.appendChild(imgSource);
    }
  }
}

234932EC-C4B0-4FA5-A22E-DE6E5A070007