Building a web application involves a lot of moving pieces. You have a backend server handling API calls, a frontend application running business logic, and you need to somehow make sure both are in sync and secure.

In this blog post, you’ll learn how to use Vaadin Fusion, Spring Boot, and Okta to create a full-stack web application with authentication. Specifically, you’ll learn how to:

  • Create a Spring Boot-based Vaadin Fusion app
  • Secure server endpoints with Okta
  • Use Okta Auth JS for logging in and securing Vaadin routes

Marcus Hellberg

I collaborated on this post with Marcus Hellberg, Head of Community at Vaadin. Marcus and I met years ago on the conference circuit, and we’re both from Finland. My great grandparents were Finnish and built the sauna and cabin I grew up in in Montana’s remote woods. Marcus and I share a passion for using Java and web technologies to build fast, efficient applications.

You might notice we keep saying “Vaadin Fusion” instead of “Vaadin.” I’ve always thought Vaadin was a web framework based on Google’s GWT (Google Web Toolkit). I asked Marcus about this, and he explained to me there are now two products—Vaadin Flow and Vaadin Fusion.

The classic Vaadin server-side Java API is now called Vaadin Flow. Vaadin Fusion is a new TypeScript frontend framework designed for Java backends. Both offer full-stack type safety and use the same set of UI components. Vaadin is no longer based on GWT. Instead, its components use web component standards.

Prerequisites

Table of Contents

  • What Is Vaadin Fusion?
  • Create a Spring Boot-based Vaadin Fusion Application
  • Secure Your Spring Boot Backend Services
    • Add the Okta Spring Boot Starter
    • Register an OpenID Connect Application
    • Configure Spring Security
    • Create a Vaadin Endpoint for Accessing Data
    • Call the Spring Boot Endpoint from Vaadin Fusion
    • Start Your Vaadin + Spring Boot App
  • Add a Vaadin Login Page and Restrict Access to Views
    • Create an Auth Service for Authentication
    • Create a Login View
    • Restrict View Access to Authenticated Users
  • Consume the Secure Endpoint from the Client
    • Create a Middleware to Add the Access Token JWT to Server Requests
    • Call the Secure Endpoint Methods
    • Add a Logout Link
  • Learn More About Vaadin and Spring Boot

You can find the full completed source code for this tutorial on GitHub, in the oktadeveloper/okta-vaadin-fusion-spring-boot-example repository.

What Is Vaadin Fusion?

Vaadin Fusion is an open-source, front-end framework designed specifically for Java backends. Fusion gives you type-safe access to your backend from your client app by auto-generating TypeScript interfaces for your server Java objects. It wraps REST calls in async TypeScript methods, so you can access your backend as easily as calling any TypeScript function.

End-to-end type checking means you catch any breaking changes at build time, not in production. Oh, and there’s auto-complete everywhere, so you can focus on coding, not reading API docs.

Views are written in TypeScript with LitElement, a lightweight, reactive component library.

Create a Spring Boot-based Vaadin Fusion Application

Begin by creating a new Vaadin Fusion app with the Vaadin starter wizard. It allows you to configure views, tech stack, and theme before downloading an app starter.

Rename the About view to “People” and change its URL to “people”:

Rename About to People

Go into the application settings and change the name to Vaadin Okta. Then, select TypeScript + HTML for the UI stack to get a Fusion project.

Select UI Stack

  1. Click Download, and you’ll get a zip file containing a Maven project.
  2. Open the project in your IDE.

The two important folders in the project are:

  • /frontend - This folder contains all the frontend code
  • /src/main/java - This folder includes all the backend code, which is a Spring Boot app

Start the application with the following command:

mvn

The launcher should open up the app in your default browser. If not, navigate to http://localhost:8080.

Secure Your Spring Boot Backend Services

Vaadin Fusion uses type-safe endpoints for server access. You create an endpoint by annotating a class with @Endpoint. This will export all the methods in the class and make them callable from TypeScript. Vaadin will also generate TypeScript interfaces for any data types the methods use.

Vaadin endpoints require authentication by default. You can explicitly make an endpoint class or a single method accessible to unauthenticated users by adding an @AnonymousAllowed annotation.

In this app, you want to restrict access to only authenticated users. You’ll use OpenID Connect (OIDC) and Okta to make this possible.

Add the Okta Spring Boot Starter

Add the Okta Spring Boot starter and Lombok dependencies to the <dependencies> section of your pom.xml file.

<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-boot-starter</artifactId>
    <version>1.4.0</version>
</dependency>

<!-- Only for convenience, not required for using Vaadin or Okta -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

Make sure your IDE imports the dependencies, or re-run mvn.

Register an OpenID Connect Application

Create a free Okta developer account on developer.okta.com if you don’t already have one.

Once logged in, go to Applications > Add Application and select Single-Page App.

Select Single-Page App

Configure the app settings and click Done to create the app:

Single-Page App Settings

Store the issuer in src/main/resources/application.properties by adding the following property:

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default

Configure Spring Security

Vaadin integrates with Spring Security to handle authorization. Instead of restricting access to specific routes as you would with Spring REST controllers, you need permit all traffic to /** so Vaadin can handle security.

Vaadin is configured to:

  • Serve index.html for the root path and any unmatched server route
  • Serve static assets
  • Handle authorization and cross-site request forgery (CSRF) protection in server endpoints

By default, all server endpoints require an authenticated user. You can allow anonymous access to an endpoint or a method by adding an @AnonymousAllowed annotation. You can further restrict access by adding @RolesAllowed to an endpoint or a method.

The security configuration below assumes you are only serving a Vaadin Fusion application. Suppose you are also serving Spring REST controllers or other non-Vaadin resources. In that case, you need to configure their access control separately, for instance, adding antMatchers("/api/**").authenticated() if you serve REST APIs under /api.

Create a new class SecurityConfiguration.java in the same package as Application.java with the following contents:

package com.example.application;

import com.okta.spring.boot.oauth.Okta;

import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        // @formatter:off
        web.ignoring()
          .antMatchers(HttpMethod.OPTIONS, "/**")
          .antMatchers("/**/*.{js,html,css,webmanifest}");
        // @formatter:on
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        // Vaadin handles CSRF for its endpoints

        http.csrf().ignoringAntMatchers("/connect/**")
            .and()
            .authorizeRequests()
            // allow access to everything, Vaadin will handle security
            .antMatchers("/**").permitAll()
            .and()
            .oauth2ResourceServer().jwt();
        // @formatter:on

        Okta.configureResourceServer401ResponseBody(http);
    }
}

Create a Vaadin Endpoint for Accessing Data

Now that you have the server set up for authenticating requests add a service you can call from the client app.

First, create a Person.java class to use as the data model in the com.example.application.views.people package.

package com.example.application.views.people;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Person {
    private String firstName;
    private String lastName;
}

If you aren’t using Lombok, omit the annotations and add a constructor that takes in firstName and lastName, and create getters and setters for both.

If you’re doing this tutorial in an IDE, you may need to enable annotation processing so Lombok can generate code for you. See Lombok’s instructions { Eclipse, IntelliJ IDEA } for more information.

Open PeopleEndpoint.java and replace the contents with the following:

package com.example.application.views.people;

import com.vaadin.flow.server.connect.Endpoint;

import java.util.ArrayList;
import java.util.List;

@Endpoint
public class PeopleEndpoint {

    // We'll use a simple list to hold data
    private List<Person> people = new ArrayList<>();

    public PeopleEndpoint() {
        // Add one person so we can see that everything works
        people.add(new Person("Jane", "Doe"));
    }

    public List<Person> getPeople() {
        return people;
    }

    public Person adEclipsedPerson(Person person) {
        people.add(person);
        return person;
    }
}

Vaadin will make the getPeople() and addPerson() methods available as asynchronous TypeScript methods. It will also generate a TypeScript interface for Person, so you can access the same type-information of both on the server and in the client.

Call the Spring Boot Endpoint from Vaadin Fusion

Create a view that uses the server API. Open frontend/views/people/people-view.ts and replace its code with the following:

import {
  LitElement,
  html,
  css,
  customElement,
  internalProperty,
} from 'lit-element';
import Person from '../../generated/com/example/application/views/people/Person';

import '@vaadin/vaadin-text-field';
import '@vaadin/vaadin-button';
import { Binder, field } from '@vaadin/form';
import PersonModel from '../../generated/com/example/application/views/people/PersonModel';
import { addPerson, getPeople } from '../../generated/PeopleEndpoint';

@customElement('people-view')
export class PeopleView extends LitElement {
  @internalProperty()
  private people: Person[] = [];
  @internalProperty()
  private message = '';

  // Manages form state, binds inputs to the model
  private binder = new Binder(this, PersonModel);

  render() {
    const { model } = this.binder;

    return html`
     <h1>People</h1>

     <div class="message">${this.message}</div>

     <ul>
       ${this.people.map(
      (person) => html`<li>${person.firstName} ${person.lastName}</li>`
    )}
     </ul>

     <h2>Add new person</h2>
     <div class="form">
       <vaadin-text-field
         label="First Name"
         ...=${field(model.firstName)}
       ></vaadin-text-field>
       <vaadin-text-field
         label="Last Name"
         ...=${field(model.lastName)}
       ></vaadin-text-field>
       <vaadin-button @click=${this.add}>Add</vaadin-button>
     </div>
   `;
  }

  async connectedCallback() {
    super.connectedCallback();
    try {
      this.people = await getPeople();
    } catch (e) {
      this.message = `Failed to get people: ${e.message}.`;
    }
  }

  async add() {
    try {
      const saved = await this.binder.submitTo(addPerson);
      if (saved) {
        this.people = [...this.people, saved];
        this.binder.clear();
      }
    } catch (e) {
      this.message = `Failed to save: ${e.message}.`;
    }
  }

  static styles = css`
   :host {
     display: block;
     padding: var(--lumo-space-m) var(--lumo-space-l);
   }
 `;
}

Here’s what this code does:

  • Defines two internal properties: people and message to hold the component’s state. Any time a property changes, the template will get re-rendered efficiently.
  • Initialized a Binder for handling the new-person form. It keeps track of the model value, handles validations, and submits the value to the endpoint.
  • The template:
  • Lists all people in an unordered list (<ul>)
  • Displays a form for adding new people. The form uses two Vaadin components: vaadin-text-field and vaadin-button. The fields are bound to the Binder with the help of a spread operator (…​=${field(…​)}). You can read more about forms in the Vaadin documentation
  • The Add button calls the add() method, which submits the form to the backend and adds the saved Person to the people array.
  • If any of the server calls fail, message gets populated to inform the user.

#spring-boot #security #programming #developer

Building and Securing a Vaadin Fusion and Spring Boot App
11.30 GEEK