Search code examples
spring-securitykeycloakvaadinvaadin-flow

Use Keycloak to login and access backend API


I am trying to connect a Vaadin frontend to a backend API with Keycloak as a centralized authorization server.

Currently I have an API-Service written in FastAPI which can be used to submit data and receive the results of an analysis for it after the data has been processed. The results are stored in different database schemas per tenant/usergroup. This API should be used to integrate the service in many ways, e.g other applications.

Now I want to provide a user interface with Vaadin so that my APIs functionality is easier to use for non-technical users. I would like to allow a login to the application with the credentials stored in Keycloak. I have prior experience with Vaadin from another plain Java application, but I'm new to using Spring, Spring Security and OAuth2/Keycloak.

I think the flow should be:

  1. A user tries to log into the UI.
  2. The UI connects to Keycloak for a specified Realm and tries to authenticate the User.
  3. If sucessful, the User is logged in.
  4. The application now calls the API to load some data to display by using the Users access token.
  5. The API verifies the token and returns this users data.

I managed to implement the login for the user following these blogs: https://martinelli.ch/vaadin-oauth2-and-keycloak/ https://rene-wilby.de/blog/hilla-oauth-authorization-code-flow-keycloak/

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {

    private final KeycloakLogoutHandler keycloakLogoutHandler;

    public SecurityConfiguration(KeycloakLogoutHandler keycloakLogoutHandler) {
        this.keycloakLogoutHandler = keycloakLogoutHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(
                authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll());

        // Icons from the line-awesome addon
        http.authorizeHttpRequests(authorize -> authorize
                .requestMatchers(new AntPathRequestMatcher("/line-awesome/**/*.svg")).permitAll());

        http.oauth2Login(Customizer.withDefaults()).logout( logout ->
                logout.addLogoutHandler(keycloakLogoutHandler)

        );

        super.configure(http);

        setOAuth2LoginPage(http, "/oauth2/authorization/keycloak");
    }

    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
        return authorities -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
            var authority = authorities.iterator().next();
            if (authority instanceof OidcUserAuthority oidcUserAuthority) {
                var userInfo = oidcUserAuthority.getUserInfo();
                if (userInfo.hasClaim("realm_access")) {
                    var realmAccess = userInfo.getClaimAsMap("realm_access");
                    var roles = (Collection<String>) realmAccess.get("roles");
                    mappedAuthorities.addAll(roles.stream()
                            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
                            .toList());
                }
            }
            return mappedAuthorities;
        };
    }
}

I receive an OicdUser and am able to access it's information. However, the OicdUser only contains an ID-Token, which is not what should be sent to the API (from my understanding).

How can I receive the access-token and use it to make a request to the backend API? Is that a proper approach to the problem or do I have some misunderstandings?

Thanks in advance!


Solution

  • You can get access tokens from the OAuth2AuthorizedClientManager bean.

    To query a resource server, you'll need a REST client, probably one of RestClient, WebClient, RestTemplate or @FeignClient (the last two being in maintenance mode and should probably be avoided in new integrations). Each has its own instructions for integrating with authorized clients, refer to the docs of the one you choose.

    This starter I just wrote provides with some auto-configuration for OAuth2 authorization with RestClient and WebClient. You can give it a try.