DEV Community

AISSAM ASSOUIK
AISSAM ASSOUIK

Posted on

Integration: Vaadin OAuth2 Authentication with Keycloak

Integrate your Vaadin Flow application with an IAM solution that supports OAuth2 protocol can be an exhaustive task since the docs are not straight forward on "How to do it?". It worth to mention that this tutorial is based on Vaadin Flow v24 docs: Securing Spring Boot Applications and OAuth2 Authentication.

Keycloak Client Config

Starting with client's general settings in Clients > Client details:

Then we create a client role in Clients > Client details.

To make our client roles visible in access token we should go to Client scopes > Client scope details > Mapper details. In Client scopes, we select Roles then we go Mappers tab and select client roles. We enable Add to access token and Add to userinfo to have client roles visible in our Vaadin Flow application by either getting from:

Map<String, Object> claimsFromAccessToekn = oidcUser.getClaims();
Map<String, Object> claimsFromUserInfo = oidcUser.getUserInfo().getClaims();
Enter fullscreen mode Exit fullscreen mode

We can do the same for realm roles as well if we need realm roles in our Vaadin Flow app.

Depencdencies

We need to add both next dependencies to our pom.xml:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
Enter fullscreen mode Exit fullscreen mode

Vaadin Flow App Config

To link our application to Keycloak OAuth2 provider we need to set the following in our application.properties file:

spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=<client-id>
spring.security.oauth2.client.registration.keycloak.client-secret=<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_HOST:PORT/realms/<REALM NAME>
Enter fullscreen mode Exit fullscreen mode

Security Config

We need to use VaadinSecurityConfigurer to configure the login page using the syntax /oauth2/authorization/{registrationId}, where registrationId is the OAuth2 client we have in application.properties which is keycloak. In our config below for we redirect to {baseUrl} by default as post-logout redirect URL.

@EnableWebSecurity
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz -> authz
                .requestMatchers("/images/*.png").permitAll()
                .requestMatchers("/icons/*.svg").permitAll()
        );

        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            configurer.oauth2LoginPage(
                    "/oauth2/authorization/keycloak"
            );
        });

        return http.build();

    }
Enter fullscreen mode Exit fullscreen mode

Keycloak Oidc User Service

Most important part is to make the roles from Keycloak available in our OidcUser instance to be able to protect views with annotations like @RolesAllowed("ROLE1").

@Component
public class KeycloakOidcUserService extends OidcUserService {

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        OidcUser oidcUser = super.loadUser(userRequest);

        Set<GrantedAuthority> mappedAuthorities = new HashSet<>(oidcUser.getAuthorities());

        Map<String, Object> claims = oidcUser.getClaims();

        // 1) realm roles: claims.realm_access.roles
        if (claims.containsKey("realm_access")) {
            Object realmAccessObj = claims.get("realm_access");
            if (realmAccessObj instanceof Map<?, ?> realmAccess) {
                Object rolesObj = realmAccess.get("roles");
                if (rolesObj instanceof Collection) {
                    ((Collection<?>) rolesObj).forEach(r -> {
                        String roleName = String.valueOf(r);
                        mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + roleName));
                    });
                }
            }
        }

        // 2) client roles: claims.resource_access.<client-id>.roles
        if (claims.containsKey("resource_access")) {
            Object resourceAccessObj = claims.get("resource_access");
            if (resourceAccessObj instanceof Map<?, ?> resourceAccess) {
                String clientId = userRequest.getClientRegistration().getClientId();
                Object clientObj = resourceAccess.get(clientId);
                if (clientObj instanceof Map<?, ?> clientMap) {
                    Object rolesObj = clientMap.get("roles");
                    if (rolesObj instanceof Collection) {
                        ((Collection<?>) rolesObj).forEach(r -> {
                            String roleName = String.valueOf(r);
                            mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + roleName));
                        });
                    }
                }
            }
        }

        return new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

This article is a hands-on guide to hooking a Vaadin Flow app up to Keycloak using OAuth2: it walks through the Keycloak client settings you need, the Spring Boot dependencies to add, and the application.properties OAuth2 registration values. It also shows the Spring Security / Vaadin config (how to point the login page to /oauth2/authorization/keycloak) and provides a KeycloakOidcUserService that reads realm and client roles from the OIDC claims and maps them to ROLE_… authorities so you can use @RolesAllowed on Vaadin views.

Top comments (0)