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:
- Client ID: client-id
- Root URL: http://VAADIN_APP_HOST:PORT
- Home URL: http://VAADIN_APP_HOST:PORT
- Valid redirect URIs: http://VAADIN_APP_HOST:PORT/*
- Valid post logout redirect URIs: http://VAADIN_APP_HOST:PORT/*
- Client authentication: On
- Authorization: On
- Authentication Flow: Standard flow
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();
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>
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>
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();
}
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());
}
}
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)