Vaadin 24.4 integrates with React, unifies Flow and Hilla development, and more!
Blog

A minimal OpenID Connect (OIDC)-secured Vaadin Flow application with Spring Boot

By  
Samuli Penttilä
·
On May 21, 2024 12:35:21 PM
·

So, do you want or need to build a Java-based web application that is secured by a Single Sign-On (SSO) login? This is a very typical use case where you have part of an application that is not public and requires an authenticated user to access it.

You could build a login mechanism yourself, but this means handling all security issues that arise on your own. If there are no compelling reasons to do so, you should use existing tools.

If you prefer, you can also watch this tutorial on YouTube, linked below.

The tech stack might look like this:

  • OpenID Connect or OIDC
    • Identity authentication protocol that is an extension of Open Authorization 2, or OAuth2.
  • Keycloak
    • Free and open source identity and access management system that supports OIDC
  • Spring Boot
    • Convention-over-configuration extension for Spring Java platform.
  • Vaadin Flow
    • Open source Java framework for building web applications.

But how do you know where to start?

The examples that you will find online are either incomplete or overwhelming. If they are incomplete, then they are not fully working apps that you can play around with. If they are overwhelming, you can play around, but you can easily get lost with regard to what does exactly what.

Start simple

Ideally, it would be really nice to have a starting point that gave you a minimal but working application. This would bring you much closer to starting from scratch, where you understand every line that goes into your application. This minimalism-themed tutorial is all about taking something complex and making it simple and understandable.

Let’s try to make it simple.

In practical terms

The project layout contains three required files:

Everything else will either be generated or will not be strictly required.

Let’s consider what the application does. It has two routes: an unsecured main page and a secured page that displays the username of the logged-in user.

Routes

The first route is for the secured part that should be login enforced. The annotation @PermitAll allows access for any logged-in user. The route /logout is configured by Spring behind the scenes and is implemented by convention.

The second route is for the unsecured, public part, and accessing it does not require the user to log in. The annotation @AnonymousAllowed specifies that it is not necessary to be logged in.

@Route("secured")
@PermitAll
public class SecuredRoute extends Div {
    public SecuredRoute() {
        OidcUser user = (OidcUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        add(new Paragraph("This is a secured route and you are the user '%s'".formatted(user.getName())));
        Anchor logoutLink = new Anchor("/logout", "logout");

        // /logout is handled by Spring and not Vaadin
        logoutLink.setRouterIgnore(true); 
        add(logoutLink);
    }
}

@Route("unsecured")
@RouteAlias("")
@AnonymousAllowed
public class UnsecuredRoute extends Div {
    public UnsecuredRoute() {
        add(new Paragraph("Welcome to unsecured route. This you may access without logging in."));
        Anchor linkToSecuredPage = new Anchor("/secured", "This route will require you to log in");

        // So that the Spring Security web filter will catch it
        linkToSecuredPage.setRouterIgnore(true); 
        add(linkToSecuredPage);
    }
}

Security configuration

Spring configuration is required to configure OIDC, and the VaadinWebSecurity base class is inherited here, because it configures Vaadin to play along with Spring Security. The Vaadin.com documentation page describes the usage and details of this base class.

If the logout route is navigated, the user would stay on the final page, but we want to redirect back to the /unsecured route and the logout success handler here does exactly that.

Spring Security will add filters to catch unauthorized access and redirect it to the Keycloak endpoints. Finally, the OAuth2 login has the default configuration with by-convention values.

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {
    private final OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler;

    public SecurityConfiguration(@Autowired ClientRegistrationRepository clientRegistrationRepository) {
        logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        // Where Keycloak will redirect after logging out
        logoutSuccessHandler.setPostLogoutRedirectUri("http://localhost:8080/unsecured"); 
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // This is important to let Spring Security know to redirect to external login page.
        http.oauth2Login(Customizer.withDefaults()); 

        // Logout with oauth2 must be handled with Keycloak
        http.logout(c -> c.logoutSuccessHandler(logoutSuccessHandler)); 
        super.configure(http);
    }
}

Keycloak properties

The most interesting properties are client_id and issuer-uri, which will be pointing to the Keycloak configuration, which will be done in the next chapter. The property issuer-URI contains both the address of the Keycloak server and the realm as a part of the URI.

spring.security.oauth2.client.registration.keycloak.client-id=minimal-spring-oidc-client

spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.keycloak.scope=openid

spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8081/realms/minimal-spring-oidc

spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

Prerequisites: OpenID Connect server

You will need to have some instance of the Keycloak server running. If you don't, then use the Keycloak development Docker image like this:

docker run -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:23.0.7 start-dev

and follow the guide for setting up the realm, client-id, and user, like so: https://www.keycloak.org/getting-started/getting-started-docker

To run it

The application.properties that were defined in the previous chapter expect a Keycloak server running with client ID " minimal-spring-oidc-client" at URL http://localhost:8081/realms/minimal-spring-oidc

Run the application with the command:

mvn spring-boot:run

and then open your browser at http://localhost:8080/

Happy coding! If you have any questions, feel free to comment below.

sso
Samuli Penttilä
Samuli Penttilä has been at Vaadin since 2011 and is working for consulting and customer projects.
Other posts by Samuli Penttilä