Blog

OpenID Connect authentication & Vaadin - An integration example using Quarkus

By  
Matti Tahvonen
Matti Tahvonen
·
On Sep 12, 2023 6:51:46 PM
·

This blog post discusses the advantages of using OpenID Connect (OIDC) for handling authentication in web applications. It also provides an example of integrating OIDC with Vaadin using Quarkus, emphasizing the importance of security libraries and best practices when working with OIDC.

OpenID Connect (OIDC) is a standard that helps distributed web systems handle authentication, authorization, and single sign-on in a well-performing and secure manner.

The OIDC Provider/Server handles the authentication using Authorization code flow and shares a cryptographically signed ID token, which essentially contains the information logged in and may include roles, profiles, and other user details. The actual applications can verify the tokens without hitting the provider, keeping the inter-server communication and database access in control. OIDC Provider also issues an access token that applications can use to access another service on behalf of the currently authenticated user.

Internet giants such as Google and Microsoft can work as your OpenId Connect provider, but in many cases, companies want to host their own servers. There are popular and battle-proven open source implementations such as Keycloak and Hydra, or you can buy it as a service via your favorite Cloud service provider.

The flexibility of the OIDC protocol allows you to use it in many different ways. The tokens can be saved in the application memory of a server-side web app or a native Android app. Alternatively, browser cookies can be shared with the server and then forwarded to the service layer. But handling tokens should always be done with care! Leaking one is basically a key to all related services and might expose sensitive information in custom claims. Cryptographically signed tokens are only Base64URL encoded. For example, any third-party JS can access your tokens stored in LocalStorage (or non-httpOnly cookie). Avoid that, even though it is suggested in some tutorials!

For stateful web apps, the most secure and best-performing approach to token handling typically does not involve storing them in the browser. But as frameworks like Spring Security and Quarkus Security aim to support stateless apps as well, their approach is to store them in a httpOnly session cookie, which is then sent to the server with every request.

This approach introduces some overhead, which typically doesn't have a significant impact. Moreover, it provides protection against XSS attacks since JavaScript cannot access the specially flagged cookie in the browser.

Storing authentication in a cookie also may improve your developer experience as you don’t have to log back into your application in case your server-side session is discarded. Note that Quarkus OIDC also encrypts the session cookie containing the tokens by default, so custom claims from the tokens couldn’t be read, even without the httpOnly flag in the cookie.

As the most secure code is the one you don’t write, the best practice with Vaadin is also to rely on these popular security libraries, even if a slightly more optimal (and theoretically more secure) code could be achieved with other means. But be sure to configure them right and follow quality instructions: usually, the vendor’s own manuals are the best sources. If you happen to be using Vaadin on Spring Boot, you can also consider taking a shortcut with the Vaadin SSO Kit.

Microservices & OIDC

One of the big reasons why OpenID Connect is so popular these days is that it makes it fairly simple to implement authentication & authorization in microservices environments. In these setups, the token provided by the web front-end or some native app is passed forward to services, possibly transitively, in multiple steps.

As the services can cryptographically verify the provided JWT tokens independently, each and every service doesn't need to visit a separate authentication service or do expensive DB queries.

Even though Vaadin UIs don't require separate REST services, it is common in larger systems that you sometimes need to access a service over the network with a pure Java API. In this setup, your service probably uses the same OIDC server, and you'll pass the current user's access token as a 'bearer token' with your REST call to obtain user-specific responses from the service. In a microservice architecture, the initial service can then pass the token to a second layer of services so that they also know who the actual user being served is.

Integration example with Vaadin & Quarkus

As some Vaadin users have been requesting an OIDC example for Quarkus, and we don’t support Quarkus apps in our commercial SSO Kit, I decided to build an example application from bits and discuss its implementation in this post. Even if you're not running your Vaadin app on top of Quarkus, this example can help you grasp and apply the concepts to your platform.

Architectural diagram for an OIDC example.As an OpenId Connect server, I’m using Keycloak. It is an open source server for identity and access management. It happens to be implemented with Quarkus, but it is also a popular choice for other web applications. There is first-class support for Keycloak also in our SSO Kit for Spring Boot.

An advantage of using your own Keycloak server is the ability to connect additional OIDC servers as identity providers, enabling users to log in using services like Google or GitHub.

The microservice in the example is also built with Quarkus, but like the OIDC server, it could just as well be implemented with PHP. It is a loosely coupled service that serves the web UI and verifies tokens issued by the OIDC server.

Here is a simplified sequence diagram showing how Vaadin redirects the user to Keycloak for authentication, checks permissions for views, and passes over the user's authentication token to the microservice using the same OIDC server.

A simplified sequence diagram showing how Vaadin redirects the user to Keycloak for authentication, checks permissions for views, and passes over the user's authentication token to the microservice using the same OIDC server.

Now let’s see the actual pieces that put this all together:

  1. Dependencies
    You’ll need at least the Quarkus oidc extension to get started with your web UI. You can either add it using Quarkus CLI (quarkus extension add 'oidc') or add the dependency io.quarkus:quarkus-oidc to your pom.xml. Additionally, if you plan to access your microservices via the web-ui, add io.quarkus:quarkus-rest-client-reactive and io.quarkus:quarkus-oidc-token-propagation.
  2. Application.properties, Quarkus Dev mode integration with Keycloak
    In a production environment, you’ll define the OIDC server address with quarkus.oidc.auth-server-url key in your application.properties file. The cool superpower of Quarkus Dev mode is that you can leave it away for development mode. As long as you have Docker installed and running, Quarkus Dev mode will launch a Keycloak server for you to a random port and wire it for your application. True developer joy!

    However, the following configuration properties are relevant to get the system running. For example, we let the OIDC integration know that we are building a web app instead of a service. This results in it securely storing the tokens from the server in an httpOnly, encrypted cookie in the browser, and mapping roles from the token to the HTTP request, where many libraries check for them. I'll explain the logout and protected path configuration in the following steps.
    quarkus.oidc.client-id=frontend
    
    quarkus.oidc.credentials.secret=secret
    
    quarkus.oidc.application-type=web-app
    
    quarkus.oidc.logout.path=/logout
    
    quarkus.oidc.logout.post-logout-path=/
    
    quarkus.http.auth.permission.protected.paths=/login,/logout
    
    quarkus.http.auth.permission.protected.policy=authenticated
    
  3. Configuring ViewAccessChecker
    Vaadin is a SPA. Thus, the normal method of declaring protected paths won’t work. We’ll need to configure Vaadin itself to restrict access to views as needed. Vaadin comes with a built-in ViewAccessChecker class that checks if the current user (based on standard APIs in HttpServletRequest) has rights to enter the target view based on annotations like @RolesAllowed. Spring Boot users have the luxury that it is autoconfigured if Spring Security is in use, but we’ll have to make a tiny configuration class to do it. In ViewAccessCheckerInit class we observe ServiceInitEven and configure our ViewAccessChecker to each and every UI instance:
    public void serviceInit(@Observes ServiceInitEvent serviceInitEvent) {
    
       serviceInitEvent.getSource().addUIInitListener(
    
               uiInitEvent -> uiInitEvent.getUI().addBeforeEnterListener(viewAccessChecker)
       );
    }
    

    In a way, I prefer this slightly more verbose configuration. In the event that you need to customize your access rules – for example, if you are using permissions rather than declarative role-based checks – this is where you can integrate your custom logic.

  4. Role-based access to views
    There are three UIs in the example application. The AboutView is annotated with Vaadin’s custom @AnonymousAllowed annotation. Unsurprisingly, everybody can access that view in this configuration. The BasicViewand AdminView limits the access to “user” and “admin” roles with the standard @RolesAllowed annotation.
  5. Triggering login & logout
    In a Vaadin application, we could, in theory, forward users directly from the Java code to the OIDC server whenever we want the user to log in or log out. But in this example project, I want to follow my rule of thumb with security-related code: avoid writing it. I want to reuse the code already in Quarkus OIDC integration instead.

    This part is now related to the previously mentioned quarkus.http.auth.permission.protected.paths=/login configuration in application.properties file. The ViewAccessChecker and RolesAllowed annotations already prevent access to that UI. However, this configuration also enforces Quarkus to require an authenticated user when attempting to access that view directly. We've also mapped a dummy login view to the same path, but users who haven't logged in will never reach that point because Quarkus OIDC redirects them to the actual Keycloak-provided login page.

    The easiest way to forward non-logged-in users properly to this login path, is to configure ViewAccessChecker. By calling setLoginView with a string parameter (instead of the dummy LoginView), Vaadin forwards users there whenever the application logic is trying to show a view with role-based restriction.

    The main menu in the example application only shows views with proper access, but the login button is configured to navigate to BasicView. This then initiates the login process according to the following simplified sequence diagram.

    The logout functionality is implemented in a similar manner by utilizing existing Quarkus OIDC functionality. The logout button uses the Page object in Vaadin to navigate the browser to the /logout URL. From there on, we completely rely on the Quarkus OIDC integration to complete the logout.
  6. Accessing user details
    After the user is logged in and role-based access control is in place, you likely want to provide user-specific content. There are several ways to access the user's information, such as the username. For this example, I've chosen to inject the JsonWebToken object directly into the MainLayoutand read the username through it when the user is logged in:
    if (token.getName() != null) {
    
       sessionlayout.add(
    
               new Paragraph("Current user:" + accessToken.getName()),
    
    Similarly, you can now further pass the user details down to your service layer or inject the token there. Compared to, for example, accessing Principal via the request. This approach is more versatile, allowing you to access custom claims (such as email) that you request from the OIDC server.

  7. Building UIs conditionally based on roles

    On the UI side, having only role-based declarative restrictions to certain views is often insufficient. There are situations where it makes sense to create a shared superclass for different variations of the same view, each targeted for different roles. Naturally, you can also configure UIs programmatically during their construction. The above code snippet already demonstrates this approach, and for the BasicView, I've added a snippet that illustrates how to configure the UI programmatically based on the roles. The 'identity' object is the SecurityIdentity from the Quarkus Security module.

    if(identity.getIdentity().getRoles().contains("admin")) {
    
       add(new Paragraph("You are also an admin! You can navigate to the admin view either via sidebar or the button below."));
    
    } else {
    
       add(new Paragraph("You are not an admin. If you try clicking the button below the view won't be shown (behaviour depends a bit if on dev mode (shows known views) or on production (empty screen/404)."));
    
    }
    
  8. Accessing a microservice

For the example application, I also built a scenario where your Vaadin application is consuming a separate microservice that also requires or utilizes OpenID Connect. 

The non-interesting part is in the microservice directory containing another Quarkus project. Optionally, you can start at the same time as 'quarkus dev.' It will connect to the existing Keycloak instance and provide a simple REST endpoint on port 8088. It is essentially a RestEasy template project by Quarkus with the same Quarkus OIDC module as we configured for the Vaadin UI. The application type is now “service” (the default), which essentially means that Quarkus expects the token to arrive as a “Bearer token.” In the actual REST service, we simply read the name from via the security principal (no access restrictions, but those could easily be added to application.properties). All very standard Quarkus code.

The more interesting part is back in the vaadin-ui project that consumes this REST service. We are using the handy REST client from the Microprofile API and configuring the client with a AccessTokenRequestFilter from the quarkus-oidc-token-propagation extension. For each outgoing REST call, it retrieves the current user's access token from the web app and sends it to the REST service as a bearer token in the HTTP request headers. If you click the 'Greet via REST API' button, you'll notice from the response that the separate microservice now has knowledge of the currently logged-in user.

All in all, there is no magic happening, but OIDC is not the most trivial thing in web apps, even when using mostly ready-made pieces. Perhaps my most essential tip is to have at least one person on the team who understands the core concepts and cryptographic principles used in OIDC-based authentication. 

Summary

A lot of heavy security-related text. I’m sure you didn't read it all unless you were implementing an OIDC-based security layer for your application while reading the tutorial 🙂 Let's recap quickly:

  • OpenID Connect is a good choice for implementing authentication in your web application, especially if you also have non-web clients and are utilizing microservices architecture.
  • Vaadin applications can, like any other Java web application, utilize OIDC.
  • The best approach is to rely as much as possible on proven security libraries such as Quarkus Security or Spring Security. From Vaadin’s side, you'll find ViewAccessChecker, which implements role-based access control at the view level.
  • Tokens can and, in many situations, should be passed as Bearer tokens to other services within your system, but be sure not to expose them accidentally to third parties.

Huge thanks to Sergey Beryozkin from the Quarkus team for helping spar me on this article and the example!

Check out the full source code of the example via GitHub!

Matti Tahvonen
Matti Tahvonen
Matti Tahvonen has a long history in Vaadin R&D: developing the core framework from the dark ages of pure JS client side to the GWT era and creating number of official and unofficial Vaadin add-ons. His current responsibility is to keep you up to date with latest and greatest Vaadin related technologies. You can follow him on Twitter – @MattiTahvonen
Other posts by Matti Tahvonen